# 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