1.理解Platform Channel工作原理
Flutter定義了三種不同類型的Channel,它們分別是
- BasicMessageChannel:用于傳遞字符串和變結(jié)構(gòu)化的信息掀虎。
- MethodChannel:用于傳遞方法調(diào)用(method invocation)烹玉。
- EventChannel:用于數(shù)據(jù)流(event streams)的通信二打。
三種Channel之間互相獨(dú)立继效,各有用途瑞信,但它們在設(shè)計上卻非常相近喧伞。每種Channel均有三個重要成員變量:
- name:String類型潘鲫,代表Channel的名字肋杖,也是其唯一標(biāo)識符状植。
- messager:BinaryMessenger類型怨喘,代表消息信使,是消息的發(fā)送與接收的工具梳庆。
- codec:MessageCodec類型或MethodCodec類型膏执,代表消息的編解碼器更米。
1.1. Channel name
一個Flutter應(yīng)用中可能存在多個Channel征峦,每個Channel在創(chuàng)建時必須指定一個獨(dú)一無二的name眶痰,Channel之間使用name來區(qū)分彼此。當(dāng)有消息從Flutter端發(fā)送到Platform端時因宇,會根據(jù)其傳遞過來的channel name找到該Channel對應(yīng)的Handler(消息處理器)察滑。
1.2. 消息信使:BinaryMessenger
雖然三種Channel各有用途饲化,但是他們與Flutter通信的工具卻是相同的吃靠,均為BinaryMessenger巢块。
BinaryMessenger是Platform端與Flutter端通信的工具族奢,其通信使用的消息格式為二進(jìn)制格式數(shù)據(jù)。當(dāng)我們初始化一個Channel泣栈,并向該Channel注冊處理消息的Handler時,實(shí)際上會生成一個與之對應(yīng)的BinaryMessengerHandler疼进,并以channel name為key伞广,注冊到BinaryMessenger中疼电。當(dāng)Flutter端發(fā)送消息到BinaryMessenger時蔽豺,BinaryMessenger會根據(jù)其入?yún)hannel找到對應(yīng)的BinaryMessenger沧侥,并交由其處理宴杀。
BinaryMessenger在Android端是一個接口旺罢,其具體實(shí)現(xiàn)為FlutterNativeView。而其在iOS端是一個協(xié)議罩驻,名稱為FlutterBinaryMessenger,F(xiàn)lutterViewController遵循了它护赊。
BinaryMEssenger并不知道Channel的存在惠遏,它只和BinaryMessengerHandler打交道砾跃。而Channel和BinaryMessengerHandler則是一一對應(yīng)的。由于Channel從BinaryMessengerHandler接收到的消息是二進(jìn)制格式數(shù)據(jù)节吮,無法直接使用抽高,故Channel會將該二進(jìn)制消息通過Codec(消息編解碼器)解碼為能識別的消息并傳遞給Handler進(jìn)行處理。
當(dāng)Handler處理完消息之后透绩,會通過回調(diào)函數(shù)返回result翘骂,并將result通過編解碼器編碼為二進(jìn)制格式數(shù)據(jù),通過BinaryMessenger發(fā)送會Flutter端。
1.3. 消息編解碼器:Codec
消息編解碼器Codec主要用于將二進(jìn)制格式的數(shù)據(jù)轉(zhuǎn)化為Handler能夠識別的數(shù)據(jù)煤禽,F(xiàn)lutter定義了兩種Codec:MessageCodec和MethodCodec。
1.3.1. MessageCodec
MessageCodec用于二進(jìn)制格式數(shù)據(jù)與基礎(chǔ)數(shù)據(jù)之間的編解碼。BasicMessageChannel所使用的編解碼器就是MessageCodec斤程。
Android中,MessageCodec是一個接口,定義了兩個方法:encodeMessage
接收一個特定的數(shù)據(jù)類型T,并將其編碼為二進(jìn)制數(shù)據(jù)ByteBuffer颁虐,而decodeMessage
則接收二進(jìn)制數(shù)據(jù)ByteBuffer笋籽,將其解碼為特定數(shù)據(jù)類型T容劳。
iOS中留量,其名稱為FlutterMessageCodec可岂,是一個協(xié)議,定義了兩個方法:encode
接收一個類型為id的消息,將其編碼為NSData類型,而decode
接收NSData類型消息,將其解碼為id類型數(shù)據(jù)。
MessageCodec有多種不同的實(shí)現(xiàn):
-
BinaryCodec
BinaryCodec是最為簡單的一種Codec,因?yàn)槠浞祷刂殿愋秃腿雲(yún)⒌念愋拖嗤谴啵鶠槎M(jìn)制格式(Android中為ByteBuffer屈雄,iOS中為NSData)惋嚎。實(shí)際上质况,BinaryCodec在編解碼過程中什么都沒做,只是原封不動將二進(jìn)制數(shù)據(jù)消息返回而已视哑」虺剩或許你會因此覺得BinaryCodec沒有意義,但是在某些情況下它非常有用,比如使用BinaryCodec可以使傳遞內(nèi)存數(shù)據(jù)塊時在編解碼階段免于內(nèi)存拷貝晶丘。
-
StringCodec
StringCodec用于字符串與二進(jìn)制數(shù)據(jù)之間的編解碼郁油,其編碼格式為UTF-8案站。
-
JsonMessageCodec
JsonMessageCodec用于基礎(chǔ)數(shù)據(jù)與二進(jìn)制數(shù)據(jù)之間的編解碼富岳,其支持基礎(chǔ)數(shù)據(jù)以及列表蜒灰、字典。其在iOS端使用了NSJSONSerialization作為序列化的工具网杆,而在Android端則使用了其自定義的JSONUtil與StringCodec作為序列化工具座柱。
-
StandardMessageCodec
StandardMessageCodec是BasicMessageChannel的默認(rèn)編解碼器,其支持基礎(chǔ)數(shù)據(jù)類型秋秤、二進(jìn)制數(shù)據(jù)涩咖、列表零院、字典炫惩,其工作原理在下文中詳細(xì)介紹粘咖。
1.3.2. MethodCodec
MethodCodec用于二進(jìn)制數(shù)據(jù)與方法調(diào)用(MethodCall)和返回結(jié)果之間的編解碼。MethodChannel和EventChannel所使用的編解碼器均為MethodCodec巡社。
與MessageCodec不同的是姜胖,MethodCodec用于MethodCall對象的編解碼胀瞪,一個MethodCall對象代表一次從Flutter端發(fā)起的方法調(diào)用。MethodCall有2個成員變量:String類型的method
代表需要調(diào)用的方法名稱朴肺,通用類型(Android中為Object鞍盗,iOS中為id)的arguments
代表需要調(diào)用的方法入?yún)ⅰ?/p>
由于處理的是方法調(diào)用敷存,故相比于MessageCodec谱煤,MethodCodec多了對調(diào)用結(jié)果的處理令野。當(dāng)方法調(diào)用成功時餐抢,使用encodeSuccessEnvelope
將result編碼為二進(jìn)制數(shù)據(jù)现使,而當(dāng)方法調(diào)用失敗時,則使用encodeErrorEnvelope
將error的code旷痕、message碳锈、detail編碼為二進(jìn)制數(shù)據(jù)。
MethodCodec有兩種實(shí)現(xiàn):
-
JSONMethodCodec
JSONMethodCodec的編解碼依賴于JSONMessageCodec欺抗,當(dāng)其在編碼MethodCall時售碳,會先將MethodCall轉(zhuǎn)化為字典
{"method":method,"args":args}
。其在編碼調(diào)用結(jié)果時佩迟,會將其轉(zhuǎn)化為一個數(shù)組团滥,調(diào)用成功為[result]
,調(diào)用失敗為[code,message,detail]
报强。再使用JSONMessageCodec將字典或數(shù)組轉(zhuǎn)化為二進(jìn)制數(shù)據(jù)灸姊。 -
StandardMethodCodec
MethodCodec的默認(rèn)實(shí)現(xiàn),StandardMethodCodec的編解碼依賴于StandardMessageCodec秉溉,當(dāng)其編碼MethodCall時力惯,會將method和args依次使用StandardMessageCodec編碼,寫入二進(jìn)制數(shù)據(jù)容器召嘶。其在編碼方法的調(diào)用結(jié)果時父晶,若調(diào)用成功,會先向二進(jìn)制數(shù)據(jù)容器寫入數(shù)值0(代表調(diào)用成功)弄跌,再寫入StandardMessageCodec編碼后的result甲喝。而調(diào)用失敗,則先向容器寫入數(shù)據(jù)1(代表調(diào)用失旑踔弧)埠胖,再依次寫入StandardMessageCodec編碼后的code糠溜,message和detail。
1.4. 消息處理器:Handler
當(dāng)我們接收二進(jìn)制格式消息并使用Codec將其解碼為Handler能處理的消息后直撤,就該Handler上場了非竿。Flutter定義了三種類型的Handler,與Channel類型一一對應(yīng)谋竖。我們向Channel注冊一個Handler時红柱,實(shí)際上就是向BinaryMessager注冊一個與之對應(yīng)的BinaryMessageHandler。當(dāng)消息派分到BinaryMessageHandler后蓖乘,Channel會通過Codec將消息解碼锤悄,并傳遞給Handler處理。
1.4.1. MessageHandler
MessageHandler用戶處理字符串或者半結(jié)構(gòu)化的消息嘉抒,其onMessage
方法接收一個T類型的消息铁蹈,并異步返回一個相同類型result。MessageHandler的功能比較基礎(chǔ)众眨,使用場景較少,但是其配合BinaryCodec使用時容诬,能夠方便傳遞二進(jìn)制數(shù)據(jù)消息娩梨。
1.4.2. MethodHandler
MethodHandler用于處理方法的調(diào)用,其onMessage
方法接收一個MethodCall類型消息览徒,并根據(jù)MethodCall的成員變量method
去調(diào)用對應(yīng)的API狈定,當(dāng)處理完成后,根據(jù)方法調(diào)用成功或失敗习蓬,返回對應(yīng)的結(jié)果纽什。
1.4.3. StreamHandler
StreamHandler與前兩者稍顯不同,用于事件流的通信躲叼,最為常見的用途就是Platform端向Flutter端發(fā)送事件消息芦缰。當(dāng)我們實(shí)現(xiàn)一個StreamHandler時,需要實(shí)現(xiàn)其onListen
和onCancel
方法枫慷。而在onListen
方法的入?yún)⒅腥美伲幸粋€EventSink(其在Android是一個對象,iOS端則是一個block)或听。我們持有EventSink后探孝,即可通過EventSink向Flutter端發(fā)送事件消息。
實(shí)際上誉裆,StreamHandler工作原理并不復(fù)雜顿颅。當(dāng)我們注冊了一個StreamHandler后,實(shí)際上會注冊一個對應(yīng)的BinaryMessageHandler到BinaryMessager足丢。而當(dāng)Flutter端開始監(jiān)聽事件時粱腻,會發(fā)送一個二進(jìn)制消息到Platform端庇配。Platform端用MethodCodec將該消息解碼為MethodCall,如果MethodCall的method的值為"listen"栖疑,則調(diào)用StreamHandler的onListen
方法讨永,傳遞給StreamHandler一個EventSink。而通過EventSink向Flutter端發(fā)送消息時遇革,實(shí)際上就是通過BinaryMessager的send方法將消息傳遞過去卿闹。
2. 理解消息編解碼過程
在官方文檔《Writing custom platform-specific code with platform channels》中的獲取設(shè)備電量的例子中我們發(fā)現(xiàn),Android端的返回值是java.lang.Integer
類型的萝快,而iOS端返回值則是一個NSNumber
類型的(通過NSNumber numberWithInt:
獲榷亡)。而到了Flutter端時揪漩,這個返回值自動"變成"了dart語言的int類型旋恼。那么這中間發(fā)生了什么呢?
Flutter官方文檔表示奄容,standard platform channels
使用standard messsage codec
對message
和response
進(jìn)行序列化和反序列化冰更,message
與response
可以是booleans
, numbers
, Strings
, byte buffers
,List
, Maps
等等,而序列化后得到的則是二進(jìn)制格式的數(shù)據(jù)昂勒。
所以在上文提到的例子中蜀细,java.lang.Integer
或NSNumber
類型的返回值先是被序列化成了一段二進(jìn)制格式的數(shù)據(jù),然后該數(shù)據(jù)傳遞到傳遞到flutter側(cè)后戈盈,被反序列化成了dart語言中的int
類型的數(shù)據(jù)奠衔。
Flutter默認(rèn)的消息編解碼器是StandardMessageCodec,其支持的數(shù)據(jù)類型如下:
當(dāng)message或response需要被編碼為二進(jìn)制數(shù)據(jù)時,會調(diào)用StandardMessageCodec的writeValue
方法塘娶,該方法接收一個名為value
的參數(shù)归斤,并根據(jù)其類型,向二進(jìn)制數(shù)據(jù)容器(NSMutableData或ByteArrayOutputStream)寫入該類型對應(yīng)的type值刁岸,再將該數(shù)據(jù)轉(zhuǎn)化為二進(jìn)制表示脏里,并寫入二進(jìn)制數(shù)據(jù)容器。
而message或者response需要被解碼時难捌,使用的是StandardMessageCodec的readValue方法膝宁,該方法接收到二進(jìn)制格式數(shù)據(jù)后,會先讀取一個byte表示其type根吁,再根據(jù)其type將二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為對應(yīng)的數(shù)據(jù)類型员淫。
在獲取設(shè)備電量的例子中,假設(shè)設(shè)備的電量為100击敌,當(dāng)這個值被轉(zhuǎn)化為二進(jìn)制數(shù)據(jù)時介返,會先向二進(jìn)制數(shù)據(jù)容器寫入int類型對應(yīng)的type值:3,再寫入由電量值100轉(zhuǎn)化而得的4個byte。而當(dāng)Flutter端接收到該二進(jìn)制數(shù)據(jù)時圣蝎,先讀取第一個byte值刃宵,并根據(jù)其值得出該數(shù)據(jù)為int類型,接著徘公,讀取緊跟其后的4個byte牲证,并將其轉(zhuǎn)化為dart類型的int。
對于字符串关面、列表坦袍、字典的編碼會稍微復(fù)雜一些。字符串使用UTF-8編碼得到的二進(jìn)制數(shù)據(jù)是長度不定的等太,因此會在寫入type后捂齐,先寫入一個代表二進(jìn)制數(shù)據(jù)長度的size,再寫入數(shù)據(jù)缩抡。列表和字典則是寫入type后奠宜,先寫入一個代表列表或字典中元素個數(shù)的size,再遞歸調(diào)用writeValue
方法將其元素依次寫入瞻想。
3. 理解消息傳遞過程
消息是如何從Flutter端傳遞到Platform端的呢压真?接下來我們以一次MethodChannel的調(diào)用為例,去理解消息的傳遞過程蘑险。
3.1. 消息傳遞:從Flutter到Platform
3.1.1. Dart層
當(dāng)我們在Flutter端使用MethodChannel的invokeMethod
方法發(fā)起一次方法調(diào)用時榴都,就開始了我們的消息傳遞之旅。invokeMethod
方法會將其入?yún)?code>message和arguments
封裝成一個MethodCall對象漠其,并使用MethodCodec將其編碼為二進(jìn)制格式數(shù)據(jù),再通過BinaryMessages將消息發(fā)出竿音。(注意和屎,此處提到的類名與方法名均為dart層的實(shí)現(xiàn))
上述過程最終會調(diào)用到ui.Window的_sendPlatformMessage
方法,該方法是一個native方法春瞬,其實(shí)現(xiàn)在native層柴信,這與Java的JNI技術(shù)非常類似。我們向native層發(fā)送了三個參數(shù):
name
宽气,String類型随常,代表Channel名稱data
,ByteData類型萄涯,即之前封裝的二進(jìn)制數(shù)據(jù)callback
绪氛,F(xiàn)unction類型,用于結(jié)果回調(diào)
3.1.2. Native層
到native層后涝影,window.cc的SendPlatformMessage方法接受了來自dart層的三個參數(shù)枣察,并對它們做了一定的處理:dart層的回調(diào)callback
封裝為native層的PlatformMessageResponseDart類型的response
;dart層的二進(jìn)制數(shù)據(jù)data
轉(zhuǎn)化為std::vector<uint8_t>類型數(shù)據(jù)data
;根據(jù)response
,data
以及Channel名稱name
創(chuàng)建一個PlatformMessage對象序目,并通過dart_state->window()->client()->HandlePlatformMessage
方法處理PlatformMessage對象臂痕。
dart_state->window()->client()
是一個WindowClient,而其具體的實(shí)現(xiàn)為RuntimeController猿涨,RuntimeController會將消息交給其代理RuntimeDelegate處理握童。
RuntimeDelegate的實(shí)現(xiàn)為Engine,Engine在處理Message時叛赚,會判斷該消息是否是為了獲取資源(channel等于"flutter/assets"),如果是澡绩,則走獲取資源邏輯,否則調(diào)用Engine::Delegate的OnEngineHandlePlatformMessage
方法红伦。
Engine::Delegate的具體實(shí)現(xiàn)為Shell英古,其OnEngineHandlePlatformMessage
接收到消息后,會向PlatformTaskRunner添加一個Task昙读,該Task會調(diào)用PlatformView的HandlePlatformMessage
方法召调。值得注意的是,Task中的代碼執(zhí)行在Platform Task Runner中蛮浑,而之前的代碼均執(zhí)行在UI Task Runner中唠叛。
3.2. 消息處理
PlatformView的HandlePlatformMessage
方法在不同平臺有不同的實(shí)現(xiàn),但是其基本原理是相同的沮稚。
3.2.1. PlatformViewAndroid
PlatformViewAndroid的是Platformview的子類艺沼,也是其在Android端的具體實(shí)現(xiàn)。當(dāng)PlatformViewAndroid接收到PlatformMessage類型的消息時蕴掏,如果消息中有response
(類型為PlatformMessageResponseDart)障般,則生成一個自增長的response_id
,并以response_id
為key,response
為value存入字典pending_responses_
中盛杰。接著挽荡,將channel
和data
均轉(zhuǎn)化為Java可識別的數(shù)據(jù),通過JNI向Java層發(fā)起調(diào)用即供,將response_id
定拟、channel
和data
傳遞過去。
Java層中逗嫡,被調(diào)用的代碼為FlutterNativeView (BinaryMessager的具體實(shí)現(xiàn))的handlePlatformMessage
青自,該方法會根據(jù)channel
找到對應(yīng)的BinaryMessageHandler并將消息傳遞給它處理。其具體處理過程我們已經(jīng)在上文中詳細(xì)分析過了驱证,此處不再贅述延窜。
BinaryMessageHandler處理完成后,F(xiàn)lutterNativeView會通過JNI調(diào)用native的方法抹锄,將response_data
和response_id
傳遞到native層需曾。
native層,PlatformViewAndroid的InvokePlatformMessageResponseCallback
接收到了respond_id
和response_data
。其先將response_data
轉(zhuǎn)化為二進(jìn)制結(jié)果呆万,并根據(jù)response_id
商源,從panding_responses_
中找到對應(yīng)的PlatformMessageResponseDart對象,調(diào)用其Complete
方法將二進(jìn)制結(jié)果返回谋减。
3.2.2. PlatformViewIOS
PlatformViewIOS是PlatformView的子類牡彻,也是其在iOS端的具體實(shí)現(xiàn),當(dāng)PlatformViewIOS接收到message時會交給PlatformMessageRouter處理出爹。
PlatformMessageRouter通過PlatformMessage中的channel
找到對應(yīng)的FlutterBinaryMessageHandler庄吼,并將二進(jìn)制消息其處理,消息處理完成后严就,直接調(diào)用PlatformMessage對象中的PlatformMessageResponseDart對象的Complete
方法將二進(jìn)制結(jié)果返回总寻。
3.3. 結(jié)果回傳:從Platform到Flutter
PlatformMessageResponseDart的Complete
方法向UI Task Runner添加了一個新的Task,這個Task的作用是將二進(jìn)制結(jié)果從native的二進(jìn)制數(shù)據(jù)類型轉(zhuǎn)化為Dart的二進(jìn)制數(shù)據(jù)類型response
梢为,并調(diào)用dart的callback將response
傳遞到Dart層渐行。
Dart層接收到二進(jìn)制數(shù)據(jù)后,使用MethodCodec將數(shù)據(jù)解碼铸董,并返回給業(yè)務(wù)層祟印。至此,一次從Flutter發(fā)起的方法調(diào)用就完整結(jié)束了粟害。
4. 問題解析
4.1. Platform Channel的代碼運(yùn)行在什么線程
在文章《深入理解Flutter引擎線程模型》中提及蕴忆,F(xiàn)lutter Engine自己不創(chuàng)建線程,其線程的創(chuàng)建于管理是由enbedder提供的悲幅,并且Flutter Engine要求Embedder提供四個Task Runner,分別是Platform Task Runner,UI Task Runner套鹅,GPU Task Runner和IO Task Runner。
實(shí)際上汰具,在Platform側(cè)執(zhí)行的代碼運(yùn)行在Platform Task Runner中芋哭,而在Flutter app側(cè)的代碼則運(yùn)行在UI Task Runner中。在Android和iOS平臺上郁副,Platform Task Runner跑在主線程上。因此豌习,不應(yīng)該在Platform端的Handler中處理耗時操作存谎。
4.2. Platform Channel是否線程安全
Platform Channel并非是線程安全的,這一點(diǎn)在官方的文檔也有提及肥隆。Flutter Engine中多個組件是非線程安全的既荚,故跟Flutter Engine的所有交互(接口調(diào)用)必須發(fā)生在Platform Thread。故我們在將Platform端的消息處理結(jié)果回傳到Flutter端時栋艳,需要確鼻∑福回調(diào)函數(shù)是在Platform Thread(也就是Android和iOS的主線程)中執(zhí)行的。
4.3. 是否支持大內(nèi)存數(shù)據(jù)塊的傳遞
Platform Channel實(shí)際上是支持大內(nèi)存數(shù)據(jù)塊的傳遞,當(dāng)需要傳遞大內(nèi)存數(shù)據(jù)塊時晴叨,需要使用BasicMessageChannel以及BinaryCodec凿宾。而整個數(shù)據(jù)傳遞的過程中,唯一可能出現(xiàn)數(shù)據(jù)拷貝的位置為native二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為Dart語言二進(jìn)制數(shù)據(jù)兼蕊。若二進(jìn)制數(shù)據(jù)大于閾值時(目前閾值為1000byte)則不會拷貝數(shù)據(jù)初厚,直接轉(zhuǎn)化,否則拷貝一份再轉(zhuǎn)化孙技。
4.4. 如何將Platform Channel原理應(yīng)用到開發(fā)工作中
實(shí)際上Platform Channel的應(yīng)用場景非常多产禾,我們這里舉一個例子:
在平常的業(yè)務(wù)開發(fā)中,我們需要使用到一些本地圖片資源牵啦,但是Flutter端是無法使用Platform端已存在的圖片資源的亚情。當(dāng)Flutter端需要使用一個Platform端已有的圖片資源時,只有將該圖片資源拷貝一份到Flutter的Assert目錄下才能使用哈雏。實(shí)際上楞件,讓Flutter端使用Platform端的資源并不是一件難事。
我們可以使用BasicMessageChannel來完成這個工作僧著。Flutter端將圖片資源名name傳遞給Platform端履因,Native端使用Platform端接收到name后,根據(jù)name定位到圖片資源盹愚,并將該圖片資源以二進(jìn)制數(shù)據(jù)格式栅迄,通過BasicMessageChannel葵陵,傳遞回Flutter端汰蜘。
總結(jié)
在Flutter與Native混合開發(fā)的模式下淮逊,Platform Channel的應(yīng)用場景非常多汇竭,理解Platform Channel的工作原理派歌,有助于我們在從事這方面開發(fā)時能做到得心應(yīng)手钦无。