Flutter Plugin 資料
[TOC]
前言
在Flutter中灼卢,如果我們需要打印日志蛤签,如果不進(jìn)行自定義,我們只能使用自帶的print()或者debugPrint()方法進(jìn)行打印性宏,
但是這兩種打印踩验,日志都是默認(rèn)Info層級(jí)的日志,很不友好赴恨,所以如果需要日志打印層級(jí)分明疹娶,我們就需要自定義一個(gè)
日志打印組件,但是我希望這個(gè)打日志的組件也可以以后在其他項(xiàng)目里直接拿來使用.這就需要我們來開發(fā)一個(gè)日志的插件了伦连,
再比如我們想在Flutter里面獲取Android設(shè)備的信息雨饺,或者就是想用Native實(shí)現(xiàn)一個(gè)功能钳垮,然后能在Flutter里面使用. 等等...
什么是插件
在flutter中,一個(gè)插件叫做一個(gè)package额港,使用packages的目的就是為了達(dá)到模塊化饺窿,可以創(chuàng)建出可被復(fù)用和共享的代碼,
這和大多數(shù)編程語言中的模塊移斩、包的概念相同肚医。創(chuàng)建出來的package可以在pubspec.yaml中直接依賴。
一個(gè)最小化的package包含了兩部分:
一個(gè)pubspec.yaml文件:一個(gè)元數(shù)據(jù)文件向瓷,聲明了聲明了package的名稱肠套、版本、作者等信息猖任。
一個(gè)lib文件夾:包含里package的公開代碼你稚,文件夾至少需要存在<pakcage-name>.dart這個(gè)文件。
注意:<pakcage-name>.dart這個(gè)文件必須存在朱躺,因?yàn)檫@是方便使用的人快速import這個(gè)package來使用它入宦,可以把它理解成一種必須要遵守的規(guī)則。
package的種類
package可以分為兩種:純dart代碼的package和帶有特定平臺(tái)代碼的package室琢。
- Dart packages:這是一個(gè)只有dart代碼的package乾闰,里面包含了flutter的特定功能,所以它依賴于flutter的framework盈滴,也決定了它只能用在flutter上涯肩。
- plugin packages:這是一個(gè)既包含了dart代碼編寫的api,又包含了平臺(tái)(Android/IOS)特定實(shí)現(xiàn)的package巢钓,可以被Android和ios調(diào)用病苗。
上面應(yīng)該很好理解,可以理解成java jar包和Android sdk的區(qū)別症汹。而要開發(fā)的日志插件就是第二種硫朦。
開發(fā)步驟
開發(fā) Dart packages
要?jiǎng)?chuàng)建Dart包,請(qǐng)使用--template=package 來執(zhí)行 flutter create
flutter create --template=package hello
這將在hello/文件夾下創(chuàng)建一個(gè)具有以下專用內(nèi)容的package工程:
-
lib/hello.dart:
- Package的Dart代碼
-
test/hello_test.dart:
- Package的單元測(cè)試代碼.
- 實(shí)現(xiàn)package
- 對(duì)于純Dart包背镇,只需在主lib/<package name>.dart文件內(nèi)或lib目錄中的文件中添加功能 咬展。
- 要測(cè)試軟件包,請(qǐng)?jiān)趖est目錄中添加unit tests瞒斩。
開發(fā) plugin packages
Step 1: 創(chuàng)建 package
要?jiǎng)?chuàng)建插件包破婆,請(qǐng)使用
--template=plugin
參數(shù)執(zhí)行flutter create
使用--org選項(xiàng)指定您的組織,并使用反向域名表示法胸囱。該值用于生成的Android和iOS代碼中的各種包和包標(biāo)識(shí)符祷舀。
flutter create --org com.example --template=plugin hello
這將在hello/文件夾下創(chuàng)建一個(gè)具有以下專用內(nèi)容的插件工程:
-
lib/hello.dart:
- 插件包的Dart API.
-
android/src/main/java/com/yourcompany/?hello/HelloPlugin.java:
- 插件包API的Android實(shí)現(xiàn).
-
ios/Classes/HelloPlugin.m:
- 插件包API的ios實(shí)現(xiàn).
-
example/:
- 一個(gè)依賴于該插件的Flutter應(yīng)用程序,來說明如何使用它
默認(rèn)情況下,插件項(xiàng)目針對(duì)iOS代碼使用Objective-C裳扯,Android代碼使用Java抛丽。如果您更喜歡Swift或Kotlin,則可以使用-i 或 -a 為iOS或Android指定語言饰豺。例如:
- 一個(gè)依賴于該插件的Flutter應(yīng)用程序,來說明如何使用它
flutter create --template=plugin -i swift -a kotlin hello
Step 2: 實(shí)現(xiàn)包 package
由于插件包中包含用多種編程語言編寫的多個(gè)平臺(tái)的代碼亿鲜,因此需要一些特定的步驟來確保順暢的體驗(yàn)。
Step 2a: 定義包API(.dart)
插件包的API在Dart代碼中定義哟忍。打開主文件夾hello/ 狡门。找到lib/hello.dartStep 2b: 添加Android平臺(tái)代碼(.java / .kt)
我們建議您使用Android Studio編輯Android代碼锅很。
在Android Studio中編輯Android平臺(tái)代碼之前其馏,首先確保代碼至少已經(jīng)構(gòu)建過一次(例如叛复,從IntelliJ運(yùn)行示例應(yīng)用程序或在終端執(zhí)行cd hello/example; flutter build apk)
- 啟動(dòng)Android Studio
- 在’Welcome to Android Studio’對(duì)話框選擇 ‘Import project’, 或者在菜單欄 ‘File > New > Import Project…‘,然后選擇hello/example/android/build.gradle文件.
- 在’Gradle Sync’ 對(duì)話框, 選擇 ‘OK’.
- 在’Android Gradle Plugin Update’ 對(duì)話框, 選擇 ‘Don’t remind me again for this project’.
您插件的Android平臺(tái)代碼位于 hello/java/com.yourcompany.hello/?HelloPlugin.
您可以通過按下 ? 按鈕從Android Studio運(yùn)行示例應(yīng)用程序.
- Step 2d: 連接API和平臺(tái)代碼
最后撬码,您需要將用Dart代碼編寫的API與平臺(tái)特定的實(shí)現(xiàn)連接起來汞幢。這是通過platform channels完成的驼鹅。
添加文檔
建議將以下文檔添加到所有軟件包:
- README.md:介紹包的文件
- CHANGELOG.md 記錄每個(gè)版本中的更改
- LICENSE 包含軟件包許可條款的文件
發(fā)布 packages
一旦你實(shí)現(xiàn)了一個(gè)包,你可以在Pub上發(fā)布它 森篷,這樣其他開發(fā)人員就可以輕松使用它
在發(fā)布之前输钩,檢查pubspec.yaml
、README.md
以及CHANGELOG.md
文件疾宏,以確保其內(nèi)容的完整性和正確性张足。
然后, 運(yùn)行 dry-run 命令以查看是否都準(zhǔn)備OK了:
flutter packages pub publish --dry-run
最后, 運(yùn)行發(fā)布命令:
flutter packages pub publish
Plugin 通信原理
- 在介紹Plugin前,我們先簡(jiǎn)單了解一下Flutter:
- Flutter框架包括:Framework和Engine坎藐,他們運(yùn)行在各自的Platform上。
- Framework是Dart語言開發(fā)的,包括Material Design風(fēng)格的Widgets和Cupertino(iOS-style)風(fēng)格的Widgets岩馍,以及文本碉咆、圖片、按鈕等基礎(chǔ)Widgets蛀恩;還包括渲染疫铜、動(dòng)畫、繪制双谆、手勢(shì)等基礎(chǔ)能力壳咕。
- Engine是C++實(shí)現(xiàn)的,包括Skia(二維圖形庫)顽馋;Dart VM(Dart Runtime)谓厘;Text(文本渲染)等。
實(shí)際上寸谜,F(xiàn)lutter的上層能力都是Engine提供的竟稳。Flutter正是通過Engine將各個(gè)Platform的差異化抹平。而我們今天要講的Plugin熊痴,正是通過Engine提供的Platform Channel實(shí)現(xiàn)的通信他爸。
- Plugin其實(shí)就是一個(gè)特殊的Package。Flutter Plugin提供Android或者iOS的底層封裝果善,在Flutter層提供組件功能诊笤,使Flutter可以較
方便的調(diào)取Native的模塊。很多平臺(tái)相關(guān)性或者對(duì)于Flutter實(shí)現(xiàn)起來比較復(fù)雜的部分巾陕,都可以封裝成Plugin讨跟。其原理如下
通過上圖,我們看到Flutter App是通過Plugin創(chuàng)建的Platform Channel調(diào)用的Native APIs惜论。
Platform Channel:
- Flutter App (Client)许赃,通過MethodChannel類向Platform發(fā)送調(diào)用消息;
- Android Platform (Host)馆类,通過MethodChannel類接收調(diào)用消息混聊;
- iOS Platform (Host),通過FlutterMethodChannel類接收調(diào)用消息乾巧。
PS:消息編解碼器句喜,是JSON格式的二進(jìn)制序列化,所以調(diào)用方法的參數(shù)類型必須是可JSON序列化的沟于。
PS:方法調(diào)用咳胃,也可以反向發(fā)送調(diào)用消息。
- Android Platform
FlutterActivity旷太,是Android的Plugin管理器展懈,它記錄了所有的Plugin销睁,并將Plugin綁定到FlutterView。
理解Platform Channel工作原理
Flutter定義了三種不同類型的Channel存崖,它們分別是
BasicMessageChannel:用于傳遞字符串和半結(jié)構(gòu)化的信息冻记。
MethodChannel:用于傳遞方法調(diào)用(method invocation)。
EventChannel: 用于數(shù)據(jù)流(event streams)的通信来惧。
三種Channel之間互相獨(dú)立冗栗,各有用途,但它們?cè)谠O(shè)計(jì)上卻非常相近供搀。每種Channel均有三個(gè)重要成員變量:
- name: String類型隅居,代表Channel的名字,也是其唯一標(biāo)識(shí)符葛虐。
- messager:BinaryMessenger類型胎源,代表消息信使,是消息的發(fā)送與接收的工具挡闰。
- codec: MessageCodec類型或MethodCodec類型乒融,代表消息的編解碼器。
Channel name
? 一個(gè)Flutter應(yīng)用中可能存在多個(gè)Channel摄悯,每個(gè)Channel在創(chuàng)建時(shí)必須指定一個(gè)獨(dú)一無二的name赞季,Channel之間使用name來區(qū)分彼此。當(dāng)有消息從Flutter端發(fā)送到Platform端時(shí)奢驯,會(huì)根據(jù)其傳遞過來的channel name找到該Channel對(duì)應(yīng)的Handler(消息處理器)申钩。
消息信使:BinaryMessenger
雖然三種Channel各有用途,但是他們與Flutter通信的工具卻是相同的瘪阁,均為BinaryMessager撒遣。
- BinaryMessenger是Platform端與Flutter端通信的工具,其通信使用的消息格式為二進(jìn)制格式數(shù)據(jù)管跺。當(dāng)我們初始化一個(gè)Channel义黎,并向該Channel注冊(cè)處理消息的Handler時(shí),實(shí)際上會(huì)生成一個(gè)與之對(duì)應(yīng)的BinaryMessageHandler豁跑,并以channel name為key廉涕,注冊(cè)到BinaryMessenger中。當(dāng)Flutter端發(fā)送消息到BinaryMessenger時(shí)艇拍,BinaryMessenger會(huì)根據(jù)其入?yún)hannel找到對(duì)應(yīng)的BinaryMessageHandler狐蜕,并交由其處理。
- Binarymessenger在Android端是一個(gè)接口卸夕,其具體實(shí)現(xiàn)為FlutterNativeView层释。而其在iOS端是一個(gè)協(xié)議,名稱為FlutterBinaryMessenger快集,F(xiàn)lutterViewController遵循了它贡羔。
- Binarymessenger并不知道Channel的存在廉白,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler則是一一對(duì)應(yīng)的治力。由于Channel從BinaryMessageHandler接收到的消息是二進(jìn)制格式數(shù)據(jù)蒙秒,無法直接使用勃黍,故Channel會(huì)將該二進(jìn)制消息通過Codec(消息編解碼器)解碼為能識(shí)別的消息并傳遞給Handler進(jìn)行處理宵统。
- 當(dāng)Handler處理完消息之后,會(huì)通過回調(diào)函數(shù)返回result覆获,并將result通過編解碼器編碼為二進(jìn)制格式數(shù)據(jù)马澈,通過BinaryMessenger發(fā)送回Flutter端。
消息編解碼器:Codec
? 消息編解碼器Codec主要用于將二進(jìn)制格式的數(shù)據(jù)轉(zhuǎn)化為Handler能夠識(shí)別的數(shù)據(jù)弄息,F(xiàn)lutter定義了兩種Codec:MessageCodec和MethodCodec痊班。
MessageCodec
? MessageCodec用于二進(jìn)制格式數(shù)據(jù)與基礎(chǔ)數(shù)據(jù)之間的編解碼。BasicMessageChannel所使用的編解碼器就是MessageCodec摹量。
? Android中涤伐,MessageCodec是一個(gè)接口,定義了兩個(gè)方法:encodeMessage接收一個(gè)特定的數(shù)據(jù)類型T缨称,并將其編碼為二進(jìn)制數(shù)據(jù)ByteBuffer凝果,而decodeMessage則接收二進(jìn)制數(shù)據(jù)ByteBuffer,將其解碼為特定數(shù)據(jù)類型T睦尽。iOS中器净,其名稱為FlutterMessageCodec,是一個(gè)協(xié)議当凡,定義了兩個(gè)方法:encode接收一個(gè)類型為id的消息山害,將其編碼為NSData類型,而decode接收NSData類型消息沿量,將其解碼為id類型數(shù)據(jù)浪慌。
? MessageCodec有多種不同的實(shí)現(xiàn):
BinaryCodec
BinaryCodec是最為簡(jiǎn)單的一種Codec,因?yàn)槠浞祷刂殿愋秃腿雲(yún)⒌念愋拖嗤釉颍鶠槎M(jìn)制格式(Android中為ByteBuffer权纤,iOS中為NSData)。實(shí)際上佛掖,BinaryCodec在編解碼過程中什么都沒做妖碉,只是原封不動(dòng)將二進(jìn)制數(shù)據(jù)消息返回而已〗姹唬或許你會(huì)因此覺得BinaryCodec沒有意義欧宜,但是在某些情況下它非常有用,比如使用BinaryCodec可以使傳遞內(nèi)存數(shù)據(jù)塊時(shí)在編解碼階段免于內(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ù)葵蒂、列表交播、字典,其工作原理會(huì)在下文中詳細(xì)介紹践付。
MethodCodec
? MethodCodec用于二進(jìn)制數(shù)據(jù)與方法調(diào)用(MethodCall)和返回結(jié)果之間的編解碼秦士。MethodChannel和EventChannel所使用的編解碼器均為MethodCodec。
? 與MessageCodec不同的是永高,MethodCodec用于MethodCall對(duì)象的編解碼隧土,一個(gè)MethodCall對(duì)象代表一次從Flutter端發(fā)起的方法調(diào)用。MethodCall有2個(gè)成員變量:String類型的method代表需要調(diào)用的方法名稱命爬,通用類型(Android中為Object曹傀,iOS中為id)的arguments代表需要調(diào)用的方法入?yún)ⅰ?br>
? 由于處理的是方法調(diào)用,故相比于MessageCodec遇骑,MethodCodec多了對(duì)調(diào)用結(jié)果的處理卖毁。當(dāng)方法調(diào)用成功時(shí),使用encodeSuccessEnvelope將result編碼為二進(jìn)制數(shù)據(jù)落萎,而當(dāng)方法調(diào)用失敗時(shí)亥啦,則使用encodeErrorEnvelope將error的code、message练链、detail編碼為二進(jìn)制數(shù)據(jù)翔脱。
? MethodCodec有兩種實(shí)現(xiàn):
- JSONMethodCodec
- JSONMethodCodec的編解碼依賴于JSONMessageCodec,當(dāng)其在編碼MethodCall時(shí)媒鼓,會(huì)先將MethodCall轉(zhuǎn)化為字典{"method":method,"args":args}届吁。其在編碼調(diào)用結(jié)果時(shí),會(huì)將其轉(zhuǎn)化為一個(gè)數(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時(shí)擎厢,會(huì)將method和args依次使用StandardMessageCodec編碼究流,寫入二進(jìn)制數(shù)據(jù)容器辣吃。其在編碼方法的調(diào)用結(jié)果時(shí),若調(diào)用成功芬探,會(huì)先向二進(jìn)制數(shù)據(jù)容器寫入數(shù)值0(代表調(diào)用成功)神得,再寫入StandardMessageCodec編碼后的result。而調(diào)用失敗偷仿,則先向容器寫入數(shù)據(jù)1(代表調(diào)用失斄ú尽),再依次寫入StandardMessageCodec編碼后的code炎疆,message和detail卡骂。
消息處理器:Handler
? 當(dāng)我們接收二進(jìn)制格式消息并使用Codec將其解碼為Handler能處理的消息后,就該Handler上場(chǎng)了形入。Flutter定義了三種類型的Handler,與Channel類型一一對(duì)應(yīng)缝左。我們向Channel注冊(cè)一個(gè)Handler時(shí)亿遂,實(shí)際上就是向BinaryMessager注冊(cè)一個(gè)與之對(duì)應(yīng)的BinaryMessageHandler。當(dāng)消息派分到BinaryMessageHandler后渺杉,Channel會(huì)通過Codec將消息解碼蛇数,并傳遞給Handler處理。
. MessageHandler
? MessageHandler用戶處理字符串或者半結(jié)構(gòu)化的消息是越,其onMessage方法接收一個(gè)T類型的消息耳舅,并異步返回一個(gè)相同類型result。MessageHandler的功能比較基礎(chǔ)倚评,使用場(chǎng)景較少浦徊,但是其配合BinaryCodec使用時(shí),能夠方便傳遞二進(jìn)制數(shù)據(jù)消息天梧。
MethodHandler
? MethodHandler用于處理方法的調(diào)用盔性,其onMessage方法接收一個(gè)MethodCall類型消息,并根據(jù)MethodCall的成員變量method去調(diào)用對(duì)應(yīng)的API呢岗,當(dāng)處理完成后冕香,根據(jù)方法調(diào)用成功或失敗,返回對(duì)應(yīng)的結(jié)果后豫。
StreamHandler
- StreamHandler與前兩者稍顯不同悉尾,用于事件流的通信,最為常見的用途就是Platform端向Flutter端發(fā)送事件消息挫酿。當(dāng)我們實(shí)現(xiàn)一個(gè)StreamHandler時(shí)构眯,需要實(shí)現(xiàn)其onListen和onCancel方法。而在onListen方法的入?yún)⒅蟹贡幸粋€(gè)EventSink(其在Android是一個(gè)對(duì)象鸵赖,iOS端則是一個(gè)block)务漩。我們持有EventSink后,即可通過EventSink向Flutter端發(fā)送事件消息它褪。
? 實(shí)際上饵骨,StreamHandler工作原理并不復(fù)雜。當(dāng)我們注冊(cè)了一個(gè)StreamHandler后茫打,實(shí)際上會(huì)注冊(cè)一個(gè)對(duì)應(yīng)的BinaryMessageHandler到BinaryMessager居触。而當(dāng)Flutter端開始監(jiān)聽事件時(shí),會(huì)發(fā)送一個(gè)二進(jìn)制消息到Platform端老赤。Platform端用MethodCodec將該消息解碼為MethodCall轮洋,如果MethodCall的method的值為"listen",則調(diào)用StreamHandler的onListen方法抬旺,傳遞給StreamHandler一個(gè)EventSink弊予。而通過EventSink向Flutter端發(fā)送消息時(shí),實(shí)際上就是通過BinaryMessager的send方法將消息傳遞過去开财。
理解消息編解碼過程
? 在官方文檔《Writing custom platform-specific code with platform channels》中的獲取設(shè)備電量的例子中我們發(fā)現(xiàn)汉柒,Android端的返回值是java.lang.Integer類型的,而iOS端返回值則是一個(gè)NSNumber類型的(通過NSNumber numberWithInt:獲仍瘅ⅰ)碾褂。而到了Flutter端時(shí),這個(gè)返回值自動(dòng)"變成"了dart語言的int類型历葛。那么這中間發(fā)生了什么呢正塌?
? Flutter官方文檔表示,standard platform channels使用standard messsage codec對(duì)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ù)類型如下:
- 平臺(tái)通道數(shù)據(jù)類型支持和解碼器
- 標(biāo)準(zhǔn)平臺(tái)通道使用標(biāo)準(zhǔn)消息編解碼器粮宛,以支持簡(jiǎn)單的類似JSON值的高效二進(jìn)制序列化,例如 booleans,numbers, Strings, byte buffers, List, Maps(請(qǐng)參閱StandardMessageCodec了解詳細(xì)信息)卖宠。 當(dāng)您發(fā)送和接收值時(shí)巍杈,這些值在消息中的序列化和反序列化會(huì)自動(dòng)進(jìn)行。
下表顯示了如何在宿主上接收Dart值扛伍,反之亦然:
Dart | Android | iOS |
---|---|---|
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
int, if 64 bits not enough | java.math.BigInteger | FlutterStandardBigInteger |
double | java.lang.Double | NSNumber numberWithDouble: |
String j | ava.lang.String | NSString |
Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
List | java.util.ArrayList | NSArray |
Map | java.util.HashMap | NSDictionary |
- 當(dāng)message或response需要被編碼為二進(jìn)制數(shù)據(jù)時(shí)筷畦,會(huì)調(diào)用StandardMessageCodec的writeValue方法,該方法接收一個(gè)名為value的參數(shù),并根據(jù)其類型鳖宾,向二進(jìn)制數(shù)據(jù)容器(NSMutableData或ByteArrayOutputStream)寫入該類型對(duì)應(yīng)的type值吼砂,再將該數(shù)據(jù)轉(zhuǎn)化為二進(jìn)制表示,并寫入二進(jìn)制數(shù)據(jù)容器鼎文。
? 而message或者response需要被解碼時(shí)渔肩,使用的是StandardMessageCodec的readValue方法,該方法接收到二進(jìn)制格式數(shù)據(jù)后拇惋,會(huì)先讀取一個(gè)byte表示其type周偎,再根據(jù)其type將二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為對(duì)應(yīng)的數(shù)據(jù)類型。
? 在獲取設(shè)備電量的例子中撑帖,假設(shè)設(shè)備的電量為100蓉坎,當(dāng)這個(gè)值被轉(zhuǎn)化為二進(jìn)制數(shù)據(jù)時(shí),會(huì)先向二進(jìn)制數(shù)據(jù)容器寫入int類型對(duì)應(yīng)的type值:3胡嘿,再寫入由電量值100轉(zhuǎn)化而得的4個(gè)byte蛉艾。而當(dāng)Flutter端接收到該二進(jìn)制數(shù)據(jù)時(shí),先讀取第一個(gè)byte值灶平,并根據(jù)其值得出該數(shù)據(jù)為int類型伺通,接著,讀取緊跟其后的4個(gè)byte逢享,并將其轉(zhuǎn)化為dart類型的int。
- 對(duì)于字符串吴藻、列表瞒爬、字典的編碼會(huì)稍微復(fù)雜一些。字符串使用UTF-8編碼得到的二進(jìn)制數(shù)據(jù)是長(zhǎng)度不定的沟堡,因此會(huì)在寫入type后侧但,先寫入一個(gè)代表二進(jìn)制數(shù)據(jù)長(zhǎng)度的size,再寫入數(shù)據(jù)航罗。列表和字典則是寫入type后禀横,先寫入一個(gè)代表列表或字典中元素個(gè)數(shù)的size,再遞歸調(diào)用writeValue方法將其元素依次寫入粥血。
理解消息傳遞過程
? 消息是如何從Flutter端傳遞到Platform端的呢柏锄?接下來我們以一次MethodChannel的調(diào)用為例,去理解消息的傳遞過程复亏。
- 消息傳遞:從Flutter到Platform
Dart層
? 當(dāng)我們?cè)贔lutter端使用MethodChannel的invokeMethod方法發(fā)起一次方法調(diào)用時(shí)趾娃,就開始了我們的消息傳遞之旅。invokeMethod方法會(huì)將其入?yún)essage和arguments封裝成一個(gè)MethodCall對(duì)象缔御,并使用MethodCodec將其編碼為二進(jìn)制格式數(shù)據(jù)抬闷,再通過BinaryMessages將消息發(fā)出。(注意耕突,此處提到的類名與方法名均為dart層的實(shí)現(xiàn))
? 上述過程最終會(huì)調(diào)用到ui.Window的_sendPlatformMessage方法笤成,該方法是一個(gè)native方法评架,其實(shí)現(xiàn)在native層,這與Java的JNI技術(shù)非常類似炕泳。我們向native層發(fā)送了三個(gè)參數(shù):
- name纵诞,String類型,代表Channel名稱
- data喊崖,ByteData類型挣磨,即之前封裝的二進(jìn)制數(shù)據(jù)
- callback,F(xiàn)unction類型荤懂,用于結(jié)果回調(diào)
Native層
? 到native層后茁裙,window.cc的SendPlatformMessage方法接受了來自dart層的三個(gè)參數(shù),并對(duì)它們做了一定的處理: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)建一個(gè)PlatformMessage對(duì)象,并通過dart_state->window()->client()->HandlePlatformMessage方法處理PlatformMessage對(duì)象廊宪。
? dart_state->window()->client()是一個(gè)WindowClient矾瘾,而其具體的實(shí)現(xiàn)為RuntimeController,RuntimeController會(huì)將消息交給其代理RuntimeDelegate處理箭启。
? RuntimeDelegate的實(shí)現(xiàn)為Engine壕翩,Engine在處理Message時(shí),會(huì)判斷該消息是否是為了獲取資源(channel等于"flutter/assets"),如果是傅寡,則走獲取資源邏輯放妈,否則調(diào)用Engine::Delegate的OnEngineHandlePlatformMessage方法。
? Engine::Delegate的具體實(shí)現(xiàn)為Shell荐操,其OnEngineHandlePlatformMessage接收到消息后芜抒,會(huì)向PlatformTaskRunner添加一個(gè)Task,該Task會(huì)調(diào)用PlatformView的HandlePlatformMessage方法托启。值得注意的是宅倒,Task中的代碼執(zhí)行在Platform Task Runner中,而之前的代碼均執(zhí)行在UI Task Runner中屯耸。
消息處理
? PlatformView的HandlePlatformMessage方法在不同平臺(tái)有不同的實(shí)現(xiàn)拐迁,但是其基本原理是相同的。
. PlatformViewAndroid
? PlatformViewAndroid的是Platformview的子類肩民,也是其在Android端的具體實(shí)現(xiàn)唠亚。當(dāng)PlatformViewAndroid接收到PlatformMessage類型的消息時(shí),如果消息中有response(類型為PlatformMessageResponseDart)持痰,則生成一個(gè)自增長(zhǎng)的response_id,并以response_id為key灶搜,response為value存入字典pending_responses_中。接著,將channel和data均轉(zhuǎn)化為Java可識(shí)別的數(shù)據(jù)割卖,通過JNI向Java層發(fā)起調(diào)用前酿,將response_id、channel和data傳遞過去鹏溯。
? Java層中罢维,被調(diào)用的代碼為FlutterNativeView (BinaryMessager的具體實(shí)現(xiàn))的handlePlatformMessage坝疼,該方法會(huì)根據(jù)channel找到對(duì)應(yīng)的BinaryMessageHandler并將消息傳遞給它處理卿操。其具體處理過程我們已經(jīng)在上文中詳細(xì)分析過了,此處不再贅述碴倾。
? BinaryMessageHandler處理完成后颜阐,F(xiàn)lutterNativeView會(huì)通過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_中找到對(duì)應(yīng)的PlatformMessageResponseDart對(duì)象紫新,調(diào)用其Complete方法將二進(jìn)制結(jié)果返回。
PlatformViewIOS
? PlatformViewIOS是PlatformView的子類李剖,也是其在iOS端的具體實(shí)現(xiàn)芒率,當(dāng)PlatformViewIOS接收到message時(shí)會(huì)交給PlatformMessageRouter處理。
? PlatformMessageRouter通過PlatformMessage中的channel找到對(duì)應(yīng)的FlutterBinaryMessageHandler篙顺,并將二進(jìn)制消息其處理敲董,消息處理完成后,直接調(diào)用PlatformMessage對(duì)象中的PlatformMessageResponseDart對(duì)象的Complete方法將二進(jìn)制結(jié)果返回慰安。
結(jié)果回傳:從Platform到Flutter
? PlatformMessageResponseDart的Complete方法向UI Task Runner添加了一個(gè)新的Task,這個(gè)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é)束了键兜。