# 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