做者:閒魚技術-皓黯前端
相信讀者們在閱讀了咱們以前的文章後,對Platform Channel有了必定的理解和認識。可是因爲篇幅有限,上文並未對Platform Channel的工做原理進行詳細的講解。Platform Channel如何工做,消息如何從Flutter端傳遞到Platform端,消息如何編解碼,Platform Channel工做在什麼線程上,是否線程安全,Platform Channel可否傳遞大內存數據塊?本文試圖結合官方例子,對上述問題進行詳細的講解。java
Flutter定義了三種不一樣類型的Channel,它們分別是git
三種Channel之間互相獨立,各有用途,但它們在設計上卻很是相近。每種Channel均有三個重要成員變量:github
一個Flutter應用中可能存在多個Channel,每一個Channel在建立時必須指定一個獨一無二的name,Channel之間使用name來區分彼此。當有消息從Flutter端發送到Platform端時,會根據其傳遞過來的channel name找到該Channel對應的Handler(消息處理器)。數組
雖然三種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端。函數
消息編解碼器Codec主要用於將二進制格式的數據轉化爲Handler可以識別的數據,Flutter定義了兩種Codec:MessageCodec和MethodCodec。
MessageCodec用於二進制格式數據與基礎數據之間的編解碼。BasicMessageChannel所使用的編解碼器就是MessageCodec。
Android中,MessageCodec是一個接口,定義了兩個方法:encodeMessage
接收一個特定的數據類型T,並將其編碼爲二進制數據ByteBuffer,而decodeMessage
則接收二進制數據ByteBuffer,將其解碼爲特定數據類型T。iOS中,其名稱爲FlutterMessageCodec,是一個協議,定義了兩個方法:encode
接收一個類型爲id的消息,將其編碼爲NSData類型,而decode
接收NSData類型消息,將其解碼爲id類型數據。
MessageCodec有多種不一樣的實現:
BinaryCodec是最爲簡單的一種Codec,由於其返回值類型和入參的類型相同,均爲二進制格式(Android中爲ByteBuffer,iOS中爲NSData)。實際上,BinaryCodec在編解碼過程當中什麼都沒作,只是原封不動將二進制數據消息返回而已。或許你會所以以爲BinaryCodec沒有意義,可是在某些狀況下它很是有用,好比使用BinaryCodec可使傳遞內存數據塊時在編解碼階段免於內存拷貝。
StringCodec用於字符串與二進制數據之間的編解碼,其編碼格式爲UTF-8。
JSONMessageCodec用於基礎數據與二進制數據之間的編解碼,其支持基礎數據類型以及列表、字典。其在iOS端使用了NSJSONSerialization做爲序列化的工具,而在Android端則使用了其自定義的JSONUtil與StringCodec做爲序列化工具。
StandardMessageCodec是BasicMessageChannel的默認編解碼器,其支持基礎數據類型、二進制數據、列表、字典,其工做原理會在下文中詳細介紹。
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的編解碼依賴於JSONMessageCodec,當其在編碼MethodCall時,會先將MethodCall轉化爲字典{"method":method,"args":args}
。其在編碼調用結果時,會將其轉化爲一個數組,調用成功爲[result]
,調用失敗爲[code,message,detail]
。再使用JSONMessageCodec將字典或數組轉化爲二進制數據。
MethodCodec的默認實現,StandardMethodCodec的編解碼依賴於StandardMessageCodec,當其編碼MethodCall時,會將method和args依次使用StandardMessageCodec編碼,寫入二進制數據容器。其在編碼方法的調用結果時,若調用成功,會先向二進制數據容器寫入數值0(表明調用成功),再寫入StandardMessageCodec編碼後的result。而調用失敗,則先向容器寫入數據1(表明調用失敗),再依次寫入StandardMessageCodec編碼後的code,message和detail。
當咱們接收二進制格式消息並使用Codec將其解碼爲Handler能處理的消息後,就該Handler上場了。Flutter定義了三種類型的Handler,與Channel類型一一對應。咱們向Channel註冊一個Handler時,實際上就是向BinaryMessager註冊一個與之對應的BinaryMessageHandler。當消息派分到BinaryMessageHandler後,Channel會經過Codec將消息解碼,並傳遞給Handler處理。
MessageHandler用戶處理字符串或者半結構化的消息,其onMessage
方法接收一個T類型的消息,並異步返回一個相同類型result。MessageHandler的功能比較基礎,使用場景較少,可是其配合BinaryCodec使用時,可以方便傳遞二進制數據消息。
MethodHandler用於處理方法的調用,其onMessage
方法接收一個MethodCall類型消息,並根據MethodCall的成員變量method
去調用對應的API,當處理完成後,根據方法調用成功或失敗,返回對應的結果。
StreamHandler與前二者稍顯不一樣,用於事件流的通訊,最爲常見的用途就是Platform端向Flutter端發送事件消息。當咱們實現一個StreamHandler時,須要實現其onListen
和onCancel
方法。而在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方法將消息傳遞過去。
在官方文檔《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 codec
對message
和response
進行序列化和反序列化,message
與response
能夠是booleans
, numbers
, Strings
, byte buffers
,List
, Maps
等等,而序列化後獲得的則是二進制格式的數據。
因此在上文提到的例子中,java.lang.Integer
或NSNumber
類型的返回值先是被序列化成了一段二進制格式的數據,而後該數據傳遞到傳遞到flutter側後,被反序列化成了dart語言中的int
類型的數據。
Flutter默認的消息編解碼器是StandardMessageCodec,其支持的數據類型以下:
當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。
對於字符串、列表、字典的編碼會稍微複雜一些。字符串使用UTF-8編碼獲得的二進制數據是長度不定的,所以會在寫入type後,先寫入一個表明二進制數據長度的size,再寫入數據。列表和字典則是寫入type後,先寫入一個表明列表或字典中元素個數的size,再遞歸調用writeValue
方法將其元素依次寫入。
消息是如何從Flutter端傳遞到Platform端的呢?接下來咱們以一次MethodChannel的調用爲例,去理解消息的傳遞過程。
當咱們在Flutter端使用MethodChannel的invokeMethod
方法發起一次方法調用時,就開始了咱們的消息傳遞之旅。invokeMethod
方法會將其入參message
和arguments
封裝成一個MethodCall對象,並使用MethodCodec將其編碼爲二進制格式數據,再經過BinaryMessages將消息發出。(注意,此處提到的類名與方法名均爲dart層的實現)
上述過程最終會調用到ui.Window的_sendPlatformMessage
方法,該方法是一個native方法,其實如今native層,這與Java的JNI技術很是相似。咱們向native層發送了三個參數:
name
,String類型,表明Channel名稱data
,ByteData類型,即以前封裝的二進制數據callback
,Function類型,用於結果回調 到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中。
PlatformView的HandlePlatformMessage
方法在不一樣平臺有不一樣的實現,可是其基本原理是相同的。
PlatformViewAndroid的是Platformview的子類,也是其在Android端的具體實現。當PlatformViewAndroid接收到PlatformMessage類型的消息時,若是消息中有response
(類型爲PlatformMessageResponseDart),則生成一個自增加的response_id
,並以response_id
爲key,response
爲value存入字典pending_responses_
中。接着,將channel
和data
均轉化爲Java可識別的數據,經過JNI向Java層發起調用,將response_id
、channel
和data
傳遞過去。
Java層中,被調用的代碼爲FlutterNativeView (BinaryMessager的具體實現)的handlePlatformMessage
,該方法會根據channel
找到對應的BinaryMessageHandler並將消息傳遞給它處理。其具體處理過程咱們已經在上文中詳細分析過了,此處再也不贅述。
BinaryMessageHandler處理完成後,FlutterNativeView會經過JNI調用native的方法,將response_data
和response_id
傳遞到native層。
native層,PlatformViewAndroid的InvokePlatformMessageResponseCallback
接收到了respond_id
和response_data
。其先將response_data
轉化爲二進制結果,並根據response_id
,從panding_responses_
中找到對應的PlatformMessageResponseDart對象,調用其Complete
方法將二進制結果返回。
PlatformViewIOS是PlatformView的子類,也是其在iOS端的具體實現,當PlatformViewIOS接收到message時會交給PlatformMessageRouter處理。
PlatformMessageRouter經過PlatformMessage中的channel
找到對應的FlutterBinaryMessageHandler,並將二進制消息其處理,消息處理完成後,直接調用PlatformMessage對象中的PlatformMessageResponseDart對象的Complete
方法將二進制結果返回。
PlatformMessageResponseDart的Complete
方法向UI Task Runner添加了一個新的Task,這個Task的做用是將二進制結果從native的二進制數據類型轉化爲Dart的二進制數據類型response
,並調用dart的callback將response
傳遞到Dart層。
Dart層接收到二進制數據後,使用MethodCodec將數據解碼,並返回給業務層。至此,一次從Flutter發起的方法調用就完整結束了。
在文章《深刻理解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中處理耗時操做。
Platform Channel並不是是線程安全的,這一點在官方的文檔也有說起。Flutter Engine中多個組件是非線程安全的,故跟Flutter Engine的全部交互(接口調用)必須發生在Platform Thread。故咱們在將Platform端的消息處理結果回傳到Flutter端時,須要確保回調函數是在Platform Thread(也就是Android和iOS的主線程)中執行的。
Platform Channel其實是支持大內存數據塊的傳遞,當須要傳遞大內存數據塊時,須要使用BasicMessageChannel以及BinaryCodec。而整個數據傳遞的過程當中,惟一可能出現數據拷貝的位置爲native二進制數據轉化爲Dart語言二進制數據。若二進制數據大於閾值時(目前閾值爲1000byte)則不會拷貝數據,直接轉化,不然拷貝一份再轉化。
實際上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