深刻理解Flutter Platform Channel

做者:閒魚技術-皓黯前端

​ 相信讀者們在閱讀了咱們以前的文章後,對Platform Channel有了必定的理解和認識。可是因爲篇幅有限,上文並未對Platform Channel的工做原理進行詳細的講解。Platform Channel如何工做,消息如何從Flutter端傳遞到Platform端,消息如何編解碼,Platform Channel工做在什麼線程上,是否線程安全,Platform Channel可否傳遞大內存數據塊?本文試圖結合官方例子,對上述問題進行詳細的講解。java

1. 理解Platform Channel工做原理

Flutter定義了三種不一樣類型的Channel,它們分別是git

  • BasicMessageChannel:用於傳遞字符串和半結構化的信息。
  • MethodChannel:用於傳遞方法調用(method invocation)。
  • EventChannel: 用於數據流(event streams)的通訊。

三種Channel之間互相獨立,各有用途,但它們在設計上卻很是相近。每種Channel均有三個重要成員變量:github

  • name: String類型,表明Channel的名字,也是其惟一標識符。
  • messager:BinaryMessenger類型,表明消息信使,是消息的發送與接收的工具。
  • codec: MessageCodec類型或MethodCodec類型,表明消息的編解碼器。

1.1. Channel name

​ 一個Flutter應用中可能存在多個Channel,每一個Channel在建立時必須指定一個獨一無二的name,Channel之間使用name來區分彼此。當有消息從Flutter端發送到Platform端時,會根據其傳遞過來的channel name找到該Channel對應的Handler(消息處理器)。數組

1.2. 消息信使:BinaryMessenger

binaryMessager

​ 雖然三種Channel各有用途,可是他們與Flutter通訊的工具倒是相同的,均爲BinaryMessager。安全

​ BinaryMessenger是Platform端與Flutter端通訊的工具,其通訊使用的消息格式爲二進制格式數據。當咱們初始化一個Channel,並向該Channel註冊處理消息的Handler時,實際上會生成一個與之對應的BinaryMessageHandler,並以channel name爲key,註冊到BinaryMessenger中。當Flutter端發送消息到BinaryMessenger時,BinaryMessenger會根據其入參channel找到對應的BinaryMessageHandler,並交由其處理。app

​ Binarymessenger在Android端是一個接口,其具體實現爲FlutterNativeView。而其在iOS端是一個協議,名稱爲FlutterBinaryMessenger,FlutterViewController遵循了它。機器學習

​ Binarymessenger並不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler則是一一對應的。因爲Channel從BinaryMessageHandler接收到的消息是二進制格式數據,沒法直接使用,故Channel會將該二進制消息經過Codec(消息編解碼器)解碼爲能識別的消息並傳遞給Handler進行處理。異步

​ 當Handler處理完消息以後,會經過回調函數返回result,並將result經過編解碼器編碼爲二進制格式數據,經過BinaryMessenger發送回Flutter端。函數

1.3. 消息編解碼器:Codec

binaryMessager

​ 消息編解碼器Codec主要用於將二進制格式的數據轉化爲Handler可以識別的數據,Flutter定義了兩種Codec:MessageCodec和MethodCodec。

1.3.1. MessageCodec

​ MessageCodec用於二進制格式數據與基礎數據之間的編解碼。BasicMessageChannel所使用的編解碼器就是MessageCodec。

​ Android中,MessageCodec是一個接口,定義了兩個方法:encodeMessage接收一個特定的數據類型T,並將其編碼爲二進制數據ByteBuffer,而decodeMessage則接收二進制數據ByteBuffer,將其解碼爲特定數據類型T。iOS中,其名稱爲FlutterMessageCodec,是一個協議,定義了兩個方法:encode接收一個類型爲id的消息,將其編碼爲NSData類型,而decode接收NSData類型消息,將其解碼爲id類型數據。

​ MessageCodec有多種不一樣的實現:

  • BinaryCodec

    BinaryCodec是最爲簡單的一種Codec,由於其返回值類型和入參的類型相同,均爲二進制格式(Android中爲ByteBuffer,iOS中爲NSData)。實際上,BinaryCodec在編解碼過程當中什麼都沒作,只是原封不動將二進制數據消息返回而已。或許你會所以以爲BinaryCodec沒有意義,可是在某些狀況下它很是有用,好比使用BinaryCodec可使傳遞內存數據塊時在編解碼階段免於內存拷貝。

  • StringCodec

    StringCodec用於字符串與二進制數據之間的編解碼,其編碼格式爲UTF-8。

  • JSONMessageCodec

    JSONMessageCodec用於基礎數據與二進制數據之間的編解碼,其支持基礎數據類型以及列表、字典。其在iOS端使用了NSJSONSerialization做爲序列化的工具,而在Android端則使用了其自定義的JSONUtil與StringCodec做爲序列化工具。

  • StandardMessageCodec

    StandardMessageCodec是BasicMessageChannel的默認編解碼器,其支持基礎數據類型、二進制數據、列表、字典,其工做原理會在下文中詳細介紹。

1.3.2. MethodCodec

​ MethodCodec用於二進制數據與方法調用(MethodCall)和返回結果之間的編解碼。MethodChannel和EventChannel所使用的編解碼器均爲MethodCodec。

​ 與MessageCodec不一樣的是,MethodCodec用於MethodCall對象的編解碼,一個MethodCall對象表明一次從Flutter端發起的方法調用。MethodCall有2個成員變量:String類型的method表明須要調用的方法名稱,通用類型(Android中爲Object,iOS中爲id)的arguments表明須要調用的方法入參。

​ 因爲處理的是方法調用,故相比於MessageCodec,MethodCodec多了對調用結果的處理。當方法調用成功時,使用encodeSuccessEnvelope將result編碼爲二進制數據,而當方法調用失敗時,則使用encodeErrorEnvelope將error的code、message、detail編碼爲二進制數據。

​ MethodCodec有兩種實現:

  • JSONMethodCodec

    JSONMethodCodec的編解碼依賴於JSONMessageCodec,當其在編碼MethodCall時,會先將MethodCall轉化爲字典{"method":method,"args":args}。其在編碼調用結果時,會將其轉化爲一個數組,調用成功爲[result],調用失敗爲[code,message,detail]。再使用JSONMessageCodec將字典或數組轉化爲二進制數據。

  • StandardMethodCodec

    MethodCodec的默認實現,StandardMethodCodec的編解碼依賴於StandardMessageCodec,當其編碼MethodCall時,會將method和args依次使用StandardMessageCodec編碼,寫入二進制數據容器。其在編碼方法的調用結果時,若調用成功,會先向二進制數據容器寫入數值0(表明調用成功),再寫入StandardMessageCodec編碼後的result。而調用失敗,則先向容器寫入數據1(表明調用失敗),再依次寫入StandardMessageCodec編碼後的code,message和detail。

1.4. 消息處理器:Handler

​ 當咱們接收二進制格式消息並使用Codec將其解碼爲Handler能處理的消息後,就該Handler上場了。Flutter定義了三種類型的Handler,與Channel類型一一對應。咱們向Channel註冊一個Handler時,實際上就是向BinaryMessager註冊一個與之對應的BinaryMessageHandler。當消息派分到BinaryMessageHandler後,Channel會經過Codec將消息解碼,並傳遞給Handler處理。

1.4.1. MessageHandler

​ MessageHandler用戶處理字符串或者半結構化的消息,其onMessage方法接收一個T類型的消息,並異步返回一個相同類型result。MessageHandler的功能比較基礎,使用場景較少,可是其配合BinaryCodec使用時,可以方便傳遞二進制數據消息。

1.4.2. MethodHandler

​ MethodHandler用於處理方法的調用,其onMessage方法接收一個MethodCall類型消息,並根據MethodCall的成員變量method去調用對應的API,當處理完成後,根據方法調用成功或失敗,返回對應的結果。

1.4.3. StreamHandler

binaryMessager

​ StreamHandler與前二者稍顯不一樣,用於事件流的通訊,最爲常見的用途就是Platform端向Flutter端發送事件消息。當咱們實現一個StreamHandler時,須要實現其onListenonCancel方法。而在onListen方法的入參中,有一個EventSink(其在Android是一個對象,iOS端則是一個block)。咱們持有EventSink後,便可經過EventSink向Flutter端發送事件消息。

​ 實際上,StreamHandler工做原理並不複雜。當咱們註冊了一個StreamHandler後,實際上會註冊一個對應的BinaryMessageHandler到BinaryMessager。而當Flutter端開始監聽事件時,會發送一個二進制消息到Platform端。Platform端用MethodCodec將該消息解碼爲MethodCall,若是MethodCall的method的值爲"listen",則調用StreamHandler的onListen方法,傳遞給StreamHandler一個EventSink。而經過EventSink向Flutter端發送消息時,實際上就是經過BinaryMessager的send方法將消息傳遞過去。

2. 理解消息編解碼過程

​ 在官方文檔《Writing custom platform-specific code with platform channels》中的獲取設備電量的例子中咱們發現,Android端的返回值是java.lang.Integer類型的,而iOS端返回值則是一個NSNumber類型的(經過NSNumber numberWithInt:獲取)。而到了Flutter端時,這個返回值自動"變成"了dart語言的int類型。那麼這中間發生了什麼呢?

​ Flutter官方文檔表示,standard platform channels使用standard messsage codecmessageresponse進行序列化和反序列化,messageresponse能夠是booleans, numbers, Strings, byte buffers,List, Maps等等,而序列化後獲得的則是二進制格式的數據。

​ 因此在上文提到的例子中,java.lang.IntegerNSNumber類型的返回值先是被序列化成了一段二進制格式的數據,而後該數據傳遞到傳遞到flutter側後,被反序列化成了dart語言中的int類型的數據。

​ Flutter默認的消息編解碼器是StandardMessageCodec,其支持的數據類型以下:

binaryMessager

​ 當message或response須要被編碼爲二進制數據時,會調用StandardMessageCodec的writeValue方法,該方法接收一個名爲value的參數,並根據其類型,向二進制數據容器(NSMutableData或ByteArrayOutputStream)寫入該類型對應的type值,再將該數據轉化爲二進制表示,並寫入二進制數據容器。

​ 而message或者response須要被解碼時,使用的是StandardMessageCodec的readValue方法,該方法接收到二進制格式數據後,會先讀取一個byte表示其type,再根據其type將二進制數據轉化爲對應的數據類型。

​ 在獲取設備電量的例子中,假設設備的電量爲100,當這個值被轉化爲二進制數據時,會先向二進制數據容器寫入int類型對應的type值:3,再寫入由電量值100轉化而得的4個byte。而當Flutter端接收到該二進制數據時,先讀取第一個byte值,並根據其值得出該數據爲int類型,接着,讀取緊跟其後的4個byte,並將其轉化爲dart類型的int。

binaryMessager

​ 對於字符串、列表、字典的編碼會稍微複雜一些。字符串使用UTF-8編碼獲得的二進制數據是長度不定的,所以會在寫入type後,先寫入一個表明二進制數據長度的size,再寫入數據。列表和字典則是寫入type後,先寫入一個表明列表或字典中元素個數的size,再遞歸調用writeValue方法將其元素依次寫入。

3. 理解消息傳遞過程

​ 消息是如何從Flutter端傳遞到Platform端的呢?接下來咱們以一次MethodChannel的調用爲例,去理解消息的傳遞過程。

3.1. 消息傳遞:從Flutter到Platform

3.1.1. Dart層

​ 當咱們在Flutter端使用MethodChannel的invokeMethod方法發起一次方法調用時,就開始了咱們的消息傳遞之旅。invokeMethod方法會將其入參messagearguments封裝成一個MethodCall對象,並使用MethodCodec將其編碼爲二進制格式數據,再經過BinaryMessages將消息發出。(注意,此處提到的類名與方法名均爲dart層的實現)

​ 上述過程最終會調用到ui.Window的_sendPlatformMessage方法,該方法是一個native方法,其實如今native層,這與Java的JNI技術很是相似。咱們向native層發送了三個參數:

  • name,String類型,表明Channel名稱
  • data,ByteData類型,即以前封裝的二進制數據
  • callback,Function類型,用於結果回調

3.1.2. Native層

​ 到native層後,window.cc的SendPlatformMessage方法接受了來自dart層的三個參數,並對它們作了必定的處理:dart層的回調callback封裝爲native層的PlatformMessageResponseDart類型的response;dart層的二進制數據data轉化爲std::vector<uint8_t>類型數據data;根據response,data以及Channel名稱name建立一個PlatformMessage對象,並經過dart_state->window()->client()->HandlePlatformMessage方法處理PlatformMessage對象。

dart_state->window()->client()是一個WindowClient,而其具體的實現爲RuntimeController,RuntimeController會將消息交給其代理RuntimeDelegate處理。

​ RuntimeDelegate的實現爲Engine,Engine在處理Message時,會判斷該消息是不是爲了獲取資源(channel等於"flutter/assets"),若是是,則走獲取資源邏輯,不然調用Engine::Delegate的OnEngineHandlePlatformMessage方法。

​ Engine::Delegate的具體實現爲Shell,其OnEngineHandlePlatformMessage接收到消息後,會向PlatformTaskRunner添加一個Task,該Task會調用PlatformView的HandlePlatformMessage方法。值得注意的是,Task中的代碼執行在Platform Task Runner中,而以前的代碼均執行在UI Task Runner中。

binaryMessager

3.2. 消息處理

​ PlatformView的HandlePlatformMessage方法在不一樣平臺有不一樣的實現,可是其基本原理是相同的。

3.2.1. PlatformViewAndroid

​ PlatformViewAndroid的是Platformview的子類,也是其在Android端的具體實現。當PlatformViewAndroid接收到PlatformMessage類型的消息時,若是消息中有response(類型爲PlatformMessageResponseDart),則生成一個自增加的response_id,並以response_id爲key,response爲value存入字典pending_responses_中。接着,將channeldata均轉化爲Java可識別的數據,經過JNI向Java層發起調用,將response_idchanneldata傳遞過去。

​ Java層中,被調用的代碼爲FlutterNativeView (BinaryMessager的具體實現)的handlePlatformMessage,該方法會根據channel找到對應的BinaryMessageHandler並將消息傳遞給它處理。其具體處理過程咱們已經在上文中詳細分析過了,此處再也不贅述。

​ BinaryMessageHandler處理完成後,FlutterNativeView會經過JNI調用native的方法,將response_dataresponse_id傳遞到native層。

​ native層,PlatformViewAndroid的InvokePlatformMessageResponseCallback接收到了respond_idresponse_data。其先將response_data轉化爲二進制結果,並根據response_id,從panding_responses_中找到對應的PlatformMessageResponseDart對象,調用其Complete方法將二進制結果返回。

binaryMessager

3.2.2. PlatformViewIOS

​ PlatformViewIOS是PlatformView的子類,也是其在iOS端的具體實現,當PlatformViewIOS接收到message時會交給PlatformMessageRouter處理。

​ PlatformMessageRouter經過PlatformMessage中的channel找到對應的FlutterBinaryMessageHandler,並將二進制消息其處理,消息處理完成後,直接調用PlatformMessage對象中的PlatformMessageResponseDart對象的Complete方法將二進制結果返回。

3.3. 結果回傳:從Platform到Flutter

​ PlatformMessageResponseDart的Complete方法向UI Task Runner添加了一個新的Task,這個Task的做用是將二進制結果從native的二進制數據類型轉化爲Dart的二進制數據類型response,並調用dart的callback將response傳遞到Dart層。

​ Dart層接收到二進制數據後,使用MethodCodec將數據解碼,並返回給業務層。至此,一次從Flutter發起的方法調用就完整結束了。

4. 問題解析

4.1. Platform Channel的代碼運行在什麼線程

​ 在文章《深刻理解Flutter引擎線程模型》中說起,Flutter Engine本身不建立線程,其線程的建立於管理是由enbedder提供的,而且Flutter Engine要求Embedder提供四個Task Runner,分別是Platform Task Runner,UI Task Runner,GPU Task Runner和IO Task Runner。

​ 實際上,在Platform側執行的代碼運行在Platform Task Runner中,而在Flutter app側的代碼則運行在UI Task Runner中。在Android和iOS平臺上,Platform Task Runner跑在主線程上。所以,不該該在Platform端的Handler中處理耗時操做。

4.2. Platform Channel是否線程安全

​ Platform Channel並不是是線程安全的,這一點在官方的文檔也有說起。Flutter Engine中多個組件是非線程安全的,故跟Flutter Engine的全部交互(接口調用)必須發生在Platform Thread。故咱們在將Platform端的消息處理結果回傳到Flutter端時,須要確保回調函數是在Platform Thread(也就是Android和iOS的主線程)中執行的。

4.3. 是否支持大內存數據塊的傳遞

​ Platform Channel其實是支持大內存數據塊的傳遞,當須要傳遞大內存數據塊時,須要使用BasicMessageChannel以及BinaryCodec。而整個數據傳遞的過程當中,惟一可能出現數據拷貝的位置爲native二進制數據轉化爲Dart語言二進制數據。若二進制數據大於閾值時(目前閾值爲1000byte)則不會拷貝數據,直接轉化,不然拷貝一份再轉化。

4.4. 如何將Platform Channel原理應用到開發工做中

​ 實際上Platform Channel的應用場景很是多,咱們這裏舉一個例子:

​ 在日常的業務開發中,咱們須要使用到一些本地圖片資源,可是Flutter端是沒法使用Platform端已存在的圖片資源的。當Flutter端須要使用一個Platform端已有的圖片資源時,只有將該圖片資源拷貝一份到Flutter的Assert目錄下才能使用。實際上,讓Flutter端使用Platform端的資源並非一件難事。

​ 咱們可使用BasicMessageChannel來完成這個工做。Flutter端將圖片資源名name傳遞給Platform端,Native端使用Platform端接收到name後,根據name定位到圖片資源,並將該圖片資源以二進制數據格式,經過BasicMessageChannel,傳遞迴Flutter端。

總結

​ 在Flutter與Native混合開發的模式下,Platform Channel的應用場景很是多,理解Platform Channel的工做原理,有助於咱們在從事這方面開發時能作到駕輕就熟。

​ 最後,閒魚技術團隊廣招各種方向的達人,不管你是精通移動端,前端,後臺,仍是機器學習,音視頻,自動化測試等,都歡迎投遞簡歷加入咱們,一同用技術改善生活!

簡歷投遞:guicai.gxy@alibaba-inc.com

參考

flutter.io/platform-ch…

github.com/flutter/flu…

github.com/flutter/eng…

相關文章
相關標籤/搜索