# 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