Channel: flutter平臺層與執行層的雙向通訊

林鹿發表於2019-06-24

環境: flutter sdk v1.5.4-hotfix.1@stable

對應 flutter engine: 52c7a1e849a170be4b2b2fe34142ca2c0a6fea1f

存在這樣的情形: flutter應用的檢視控制元件響應使用者的輸入(比如KeyEvent), 需要將平臺的按鍵資料傳遞到flutter的dart環境並響應, 同時應用可能因為某個操作需要呼叫平臺的介面讓手機震動. 但是flutter的App檢視執行dart程式碼,平臺(Android)執行Java程式碼, 同時dart層無法識別java層定義的物件類, 這就需要將資料在不同的執行環境中傳遞, flutter框架中的channel機制其實就是實現這個目的的.

一些文章和部分程式碼可能會讓人感到困擾, 為什麼已經有send介面了還要新增一個setMessageHander介面, 同時send已經有回撥reply了, 怎麼MessageHandler除了資料還有帶一個reply.

理解的關鍵其實就是這個channel, 顧名思義, 就是進行資料傳送的通道, 在平臺層(java)與執行層(dart)進行資料通訊. 一旦涉及通訊就涉及物件傳遞, 而在不同執行時(runtime)環境進行物件傳遞就必然涉及物件序列化了. 所以不用被名稱迷惑, 所謂的MessageCodec其實就是專門作物件序列化的例項, 而通道既然能傳送資料也必須能夠接收資料, 如此的雙向通訊, 僅此而已.

一個通道關聯3個物件: 名稱, 操作與序列化, 操作即具體做收發訊息的工作, 即Messenger. 而訊息按型別又分為普通物件, 操作方法, 資料流, 對應著3種基本通道: BasicMessageChannel<T>, MethodChannel, EventChannel

####傳送有時機, 接收無定時 平臺端(android)可以顯式的建立一個通道, 通道建立後既可作為傳送端又可作為接收端, 作為傳送端可以主動的傳送相關資料, 是為有時機, 作為接收端, 只能被動等待資料到來, 是為無定時

####資料傳送 呼叫一個通道的send方法,即為傳送資料了, 有時傳送完資料需要一個反饋, 於是有另一個回撥引數Reply<T>, 這個回覆是接收端反饋給傳送端後傳送端作的響應, 可以叫做傳送回覆.

####資料接收 每種通道都設定了一個setMessageHandler的方法, MessageHandler<T>其實就是通道的資料接收器, 更容易理解的名字應該是MessageReceiver, 專門等待傳送端傳送的資料; 表示通道建立後作為接收方接收資料後進行的處理, 資料處理完之後可能需要再反饋給傳送端, 所以MessageHandler<T>.onMessage(T message, Reply<T> reply)中的Reply<T>是接收端反饋給傳送端的回覆, 可以叫做接收回復

####通道解碼 理解了通道本質, 通道的解碼MessageCodec就顯而易見了, 也就顯得不那麼重要了: 在資料通訊過程中針對各種各樣的資料物件進行序列化和反序列化. 我們自己也完全可以定製自己的序列方式(比如gson), 因為無論是c++層java層還是dart層, 只能讀寫位元組.

可以總結如下: 通道的本質即資料通訊 通道的解碼即對資料進行序列化和反序列化 通道可作為傳送端也可作為接收端 通道最終是以二進位制位元組的形態傳送資料 c++消彌平臺的差異(android,ios), 同時提供統一的介面和方式供dart使用

####資料傳送示例 普通物件傳遞-以Android端傳遞按鍵事件至dart端為例 按鍵資料被包裝成一個物件例項,通道物件型別是BinaryMessenger<Object>呼叫序列如下:

FlutterView.onKeyDown
  AndroidKeyProcessor.onKeyDown
    KeyEventChannel.keyDown
     BasicMessageChannel.send
       BinaryMessenger.send -> DartExecutor.send
         DartMessenger.send
           FlutterJNI.dispatchPlatformMessage
複製程式碼

最終呼叫了BinaryMessenger的send方法, 其實現體是DartExecutor, DartExecutor是平臺層與執行層互動的點, 它實現了平臺向dart呼叫, dart向平臺的響應.

呼叫dart方法-以Android端傳遞導航事件至dart端為例 activity響應開啟頁面的方法onNewIntent被flutter定義了一個導航方法,通道物件型別是MethodChannel, 呼叫序列如下:

FlutterActivityDelegate.onNewIntent
  FlutterActivityDelegate.loadIntent
    FlutterView.setInitialRoute
      NavigationChannel.setInitialRoute
        MethodChannel.invokeMethod
          new MethodCall
          JSONMethodCodec.encodeMethodCall
          BinaryMessenger.send -> DartExecutor.send
            DartMessenger.send
              FlutterJNI.dispatchPlatformMessage
複製程式碼

可以看到方法的名稱與引數被包裝成了MethodCall, 結構體被序列化成了位元組之後傳遞給dart, 最終還是呼叫了DartMessenger的send方法

此外還有EventChannel,但是在程式碼中沒有例項化(2019.06.24 flutter-engine:52c7a1e8)就先不分析了,本質與原理還是一樣的。

####響應傳送回覆 可以看到DartMessengerpendingReplies:Map<>快取了BinaryMessenger.BinaryReply, 待dart程式碼執行完傳送端操作後響應handlePlatformMessageResponse時取出, 完成傳送反饋, 在MethodChannel中即為方法返回值.

####資料接收示例 接收dart層通知 目前程式碼中只有AccessibilityChannel有用到BasicMessageChannel.MessageHandler, 這是為了設定android檢視ViewAccessibility屬性, 平常開發不怎麼用到, 但毫無疑問,最終呼叫的還是平臺層的相關程式碼

接收dart層呼叫-以Android端呼叫平臺類PlatformChannel為例 PlatformChannel負責dart層向平臺層呼叫的統一操作, 其建立過程如下

FlutterView.FltterView()
  new PlatformChannel
    new MethodChannel
    MethodChannel.setMethodCallHandler
      BinaryMessenger.setMessageHandler -> DartExecutor.setMessageHandler
        DartMessenger.setMessageHandler
  new PlatformPlugin
    PlatformChannel.setPlatformMessageHandler
複製程式碼

DartMessengermessageHandlers根據通道名稱快取了BinaryMessenger.BinaryMessageHandler, 平臺層作為接收方不定時等待dart層傳送資料, 方法呼叫流程如下:

DartMessenger.handleMessageFromDart
  BinaryMessenger.BinaryMessageHandler.onMessage -> MethodChannel.IncomingMethodCallHandler.onMessage
    MethodCallHandler.onMethodCall -> PlatformChannel.parsingMethodCallHandler.onMethodCall
      PlatformMessageHandler.vibrateHapticFeedback -> PlatformPlugin.mPlatformMessageHandler.vibrateHapticFeedback
        PlatformPlugin.vibrateHapticFeedback
          View.performHapticFeedback
複製程式碼

由上可見DartMessenger是channel機制中最為重要的核心類, 是在平臺層負責與執行層通訊的最關鍵角色.

相關文章