# flutter之channel详解

flutter提供了三个channel来支持dart与原生平台的交互,channel的通信方式类似rcp调用,不同的是flutter的内部实现是通过内存拷贝的方式将原生字节流转换成dart字节流。
  • MethodChannel

    通过定义对应的资源名称实现与平台进行一次性通信。

  • EventChannel

    通过流的方式,持续接收对方的通信数据,内部包装的MethodChannel。

  • BasicMessageChannel

    与MethodChannel类似,不同的是需要指定一个解码器,这个channel与MethodChannel没有本质区别。

# 交互原理

channel是无状态通信,一次send/reply后调用就结束了,类似http的无状态通信

# channel核心之BinaryMessenger

BinaryMessenger是flutter框架给我们提供的唯一一个用于从dart到原生消息转换的工具,所有的channel都是基于BinaryMessenger进行二次包装的,具体可以看一下BinaryMessenger提供的api。

# 下面我们自定义一个MyChannel来实现dart到原生的通信:

首先,定义个MyChannel类来包裹BinaryMessenger,实现一个channel客户端(flutter端)

class MyChannel {
    // channel客户端与服务端链接需要一个标识
    final String name;
    // channel要求通信的数据类型是ByteData
    // 所以这里需要一个解码器将消息序列化/反序列化
    final MessageCodec codec;
    // 最终消息会通过该属性发送出去
    final BinaryMessenger binaryMessenger;
    // 我们直接使用Flutter提供的唯一一个BinaryMessenger,也就是defaultBinaryMessenger实例
    const MyChannel(this.name, this.codec,
                    {this.binaryMessenger = defaultBinaryMessenger});
	
    Future<String> send(String arg) async {
        // 先将消息序列化
        var data=codec.encodeMessage(arg);
        var result =
            //发送消息
            await defaultBinaryMessenger.send(name, data);
       	// 接收返回值并反序列化
        return codec.decodeMessage(result);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

然后,定义服务端(android端)

// 定义个channel服务端与,客户端一样,都是需要发送器,解码器,以及唯一通信标识
class MyChannel( 
    private val binaryMessenger: BinaryMessenger,
    private val name:String, 
    private val messageCodec: StringCodec= StringCodec.INSTANCE) {
    // 与客户端不同的是服务端仅用于接收消息,所以我们要定义个消息处理类
    fun setHandler(binaryMessageHandler: MyBinaryMessageHandler) {
        binaryMessenger.setMessageHandler(name, binaryMessageHandler);
    }
}
// 消息处理类,收到消息后将本调用,消息处理完成后,调用reply返回响应结果
class MyBinaryMessageHandler(private val messageCodec: StringCodec) : BinaryMessenger.BinaryMessageHandler {
  override fun onMessage(arg: ByteBuffer?, reply: BinaryMessenger.BinaryReply) {
    val argStr=messageCodec.decodeMessage(arg)
    println(argStr)
    reply.reply(messageCodec.encodeMessage(argStr))
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

最后,来看一下客户端与服务的实现

flutter

// 定义一个标识为mychannel的channel
var _channel=MyChannel("mychannel",StringCodec());
// 发送消息
var result=await _channel.send("hello");
print(result)
1
2
3
4
5

android

// 在onCreate方法中创建channel监听标识为mychannel的消息
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    // 获取BinaryMessenger
    val binaryMessage=registrarFor("package top.guodf.channel_example").messenger()
    val channel=MyChannel(binaryMessage,"mychannel")
    // 创建一个MyBinaryMessageHandler用来对接收到消息进行处理
    channel.setHandler(MyBinaryMessageHandler(StringCodec.INSTANCE))
  }
1
2
3
4
5
6
7
8
9

至此,一个简单的自定义channel就实现了,这个例子包含了所有channel通信的原理,这是一个从flutter到android的实现,channel同样支持从android到flutter的通信,只要将上面的客户端与服务端代码反过来实现就行了,下面我们实现类似EventChannel的流实现。

# MyEventChannel接收服务端的持续响应

前面我们说了channel是一次行通信,那么怎么实现持续响应呢?这里我参考了EventChannel的实现,下面做一个简化版本的demo

flutter端实现

//在MyChannel中添加如下方法
Stream<String> eventStream(String msg) {
    //定义一个Stream,供flutter端持续接收服务端的消息
    var controller = StreamController<String>.broadcast();
    //这里是重点,创建一个服务端,供android调用,这个方法让flutter也变成了服务端
    defaultBinaryMessenger.setMessageHandler(name, (data) {
        if (data == null) controller.close();
        var value = codec.decodeMessage(data);
        //收到服务端消息时写入controller,监听controller.stream的都能收到通知
        controller.add(value);
        return Future.value(data);
    });
    Future(() async {
        //这里是重点,因为channel都是一次性通信,所以我们持续的通知android端我们在等待消息
        //从这里可以看到流实际是在flutter控制的
        await for (var _ in controller.stream) {
            send(msg);
        }
    });
    //第一次由flutter端发起调用激活事件流
    send(msg);
    return controller.stream;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

android端实现

//定义一个新得handler专门用于处理事件流
class MyEventMessageHandler(private val channel:MyChannel, private val messageCodec: StringCodec):BinaryMessenger.BinaryMessageHandler {
  @SuppressLint("SimpleDateFormat")
  override fun onMessage(arg: ByteBuffer?, reply: BinaryMessenger.BinaryReply) {
    val formatter = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss")
    val curDate = Date(System.currentTimeMillis())
    val str = formatter.format(curDate)
    //调用flutter端(因为flutter我们已经再监听了,所以可以收到消息)
    channel.send(str);
    //一次调用结束,通知flutter端
    reply.reply(messageCodec.encodeMessage(null))
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# 下面时事件流的一种错误实现

下面的实现虽然也可以让flutter端持续收到消息,但是无法更新widget*(还没有理解为什么 )*

flutter端

//在MyChannel中添加如下方法
Stream<String> eventStream(String msg) {
    //定义一个Stream,供flutter端持续接收服务端的消息
    var controller = StreamController<String>.broadcast();
    //这里是重点,创建一个服务端,供android调用,这个方法让flutter也变成了服务端
    defaultBinaryMessenger.setMessageHandler(name, (data) {
        if (data == null) controller.close();
        var value = codec.decodeMessage(data);
        //收到服务端消息时写入controller,监听controller.stream的都能收到通知
        controller.add(value);
        return Future.value(data);
    });
    //第一次由flutter端发起调用激活持续流
    send(msg);
    return controller.stream;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

android端的错误实现

//定义一个新得handler专门用于处理事件流
class MyEventMessageHandler(private val channel:MyChannel, private val messageCodec: StringCodec):BinaryMessenger.BinaryMessageHandler {
  @SuppressLint("SimpleDateFormat")
  override fun onMessage(arg: ByteBuffer?, reply: BinaryMessenger.BinaryReply) {
      //为了保证flutter端可以收到通知,我们将通知放在最前面
      reply.reply(messageCodec.encodeMessage(null))
      //这种方式其实也可以持续发送消息到flutter端,
      //但是会导致如果flutter端无法更新widget
      while(true){
        val formatter = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss")
        val curDate = Date(System.currentTimeMillis())
        val str = formatter.format(curDate)
        //调用flutter端(因为flutter我们已经再监听了,所以可以收到消息)
        channel.send(str);
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 总结

上面的文章已经说明了channel的同时原理及实现,至于c++部分是怎么讲dart字节与原生字节转换的我解答不了,已经超出了我的认知返回。

如果你想让MyChannel支持多种类型,可以像MethodChannel一样将MyChannel定义为泛型的版本:MyChannel<T>

个人建议还是不要自定义channel,flutter提供的三种已经完全可以满足需求了。

代码在这里:https://github.com/guodf/study_flutter/tree/master/channel