# flutter之channel详解
- 2019 年 10 月 4 日
- 筆記
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); } }
然后,定义服务端(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)) } }
最后,来看一下客户端与服务的实现
flutter
// 定义一个标识为mychannel的channel var _channel=MyChannel("mychannel",StringCodec()); // 发送消息 var result=await _channel.send("hello"); print(result)
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)) }
至此,一个简单的自定义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; }
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)) } }
下面时事件流的一种错误实现
下面的实现虽然也可以让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; }
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); } } }
总结
上面的文章已经说明了channel的同时原理及实现,至于c++部分是怎么讲dart字节与原生字节转换的我解答不了,已经超出了我的认知返回。
如果你想让MyChannel支持多种类型,可以像MethodChannel一样将MyChannel定义为泛型的版本:MyChannel<T>
。
个人建议还是不要自定义channel,flutter提供的三种已经完全可以满足需求了。
代码在这里:https://github.com/guodf/study_flutter/tree/master/channel