Flutter & Native 混合開發(fā)
項(xiàng)目集成方案
問題
- 項(xiàng)目中仍有大量業(yè)務(wù)使用 Native 開發(fā)烂翰。項(xiàng)目引入 Flutter 后蚤氏,要求所有 Native 開發(fā)者都配置 Flutter 開發(fā)環(huán)境并改動(dòng)項(xiàng)目工程結(jié)構(gòu)竿滨,會(huì)對(duì)開發(fā)效率造成影響于游。
- 引入 Flutter 后垫言,混合項(xiàng)目的構(gòu)建流程會(huì)發(fā)生變動(dòng)筷频。打包環(huán)境需要配置 Flutter 開發(fā)環(huán)境,且 Native 項(xiàng)目無法單獨(dú)構(gòu)建担忧。
需求
- 獨(dú)立開發(fā)環(huán)境
- 獨(dú)立構(gòu)建
Flutter 項(xiàng)目模式
Flutter 模式就是純 Flutter 開發(fā)模式最欠,原生項(xiàng)目被包在 Flutter 項(xiàng)目中惩猫,通過 Flutter 命令啟動(dòng)項(xiàng)目帆锋,命令會(huì)先編譯 Flutter 工程锯厢,然后將 Flutter 編譯產(chǎn)物復(fù)制到 Native 項(xiàng)目中,接著編譯 Native 捺氢,最后啟動(dòng) Native 摄乒,Native 調(diào)用 Flutter 馍佑。
module 模式拭荤,F(xiàn)lutter 工程只負(fù)責(zé)產(chǎn)出 Flutter 編譯產(chǎn)物疫诽,提供給 Native 工程使用奇徒。Flutter 和 Native 工程沒有代碼和環(huán)境依賴摩钙,Native 工程只依賴 Flutter 的編譯產(chǎn)物胖笛。Flutter 工程和 Native 工程可以獨(dú)立編譯宜肉。
我們通過 flutter create
命令創(chuàng)建新的 Flutter 工程谬返。這個(gè)命令有一個(gè) --type
參數(shù)遣铝,通過傳入 module
這個(gè)參數(shù) , 命令會(huì)為我們創(chuàng)建一個(gè) module 類型的 Flutter 項(xiàng)目酿炸。它和普通的 Flutter 項(xiàng)目只有些許不同填硕,我們可以通過項(xiàng)目結(jié)構(gòu)窺探一二扁眯。
從上面的圖片可知姻檀,F(xiàn)lutter 項(xiàng)目模式和 module 模式在項(xiàng)目結(jié)構(gòu)和文件上差不多绣版,但在 iOS 和 Android 這兩個(gè)和 Native 相關(guān)的文件夾上杂抽,F(xiàn)lutter 項(xiàng)目是直接將兩個(gè)文件夾暴露出來缩麸,而 module 模式則是選擇隱藏這兩個(gè) Native 文件夾骤素。很顯然济竹,對(duì)于 Flutter 項(xiàng)目而言送浊,Native 項(xiàng)目直接包在了 Flutter 項(xiàng)目中丘跌,必然需要在其中直接進(jìn)行 Native 頁面和邏輯的開發(fā)。而 module 模式中耸棒,整個(gè) Flutter 工程是和 Native 工程隔離開的与殃,原則上不應(yīng)該有 Native 的邏輯代碼幅疼,因而隱藏了 Native 相關(guān)文件,不讓用戶有機(jī)會(huì)觸碰或者添加 Native 代碼悴晰。
既然 module 模式和 Native 項(xiàng)目完全隔離铡溪, 為什么還要保留一個(gè)隱藏的 Native 的項(xiàng)目呢佃却?因?yàn)樵?Native 平臺(tái)上饲帅,F(xiàn)lutter 無法獨(dú)立運(yùn)行瘤泪, 始終需要依附于一個(gè) Native 項(xiàng)目才能夠啟動(dòng)对途。因此為了在 module 模式下運(yùn)行 App 進(jìn)行調(diào)試实檀,必定需要一個(gè) Native App 的空殼來調(diào)起 Flutter 頁面。
Native 調(diào)用 Flutter
要理解 module 模式如何工作恬吕,我們首先需要了解铐料,一個(gè)純 Flutter 工程是如何被 Native App 引用并調(diào)用的钠惩。
Flutter for iOS 的產(chǎn)物:
- App.framework:Dart 業(yè)務(wù)源碼相關(guān)文件篓跛,以及項(xiàng)目依賴的靜態(tài)資源愧沟,如字體,圖片等
- Flutter.framework:Flutter 引擎庫文件
- pubs 插件目錄及用于索引的文件:Flutter 下的插件计盒,包括各種系統(tǒng)的和自定義的channels
iOS Native 項(xiàng)目通過引入 Flutter.framework 北启,便可以調(diào)用 Flutter 項(xiàng)目中的代碼咕村,將 Flutter 嵌入到項(xiàng)目中懈涛。
Flutter for Android 的產(chǎn)物則是一個(gè) flutter.aar泳猬,其中同樣包含了 Flutter 引擎庫以及項(xiàng)目中用到的靜態(tài)資源等得封。Android 工程通過引入 flutter.aar 忙上,便可以調(diào)用 Flutter 項(xiàng)目中的代碼。
從上面的實(shí)踐可知茬斧,只要我們?nèi)〉昧?Flutter for Native 的編譯產(chǎn)物项秉,便可以輕松的將 Flutter 工程嵌入到現(xiàn)有的 Native 項(xiàng)目中伙狐。其中贷屎,iOS 相對(duì)簡單艘虎,直接將 Flutter 編譯出的幾個(gè)產(chǎn)物(Framwork野建、配置文件) 拖入 Native 項(xiàng)目中即可調(diào)用候生。安卓則需要修改 Native 工程中的 gradle,添加 aar 中的 Flutter 依賴须蜗。
Native 接入 Flutter 實(shí)踐
目錄結(jié)構(gòu)如下
some/path/ flutter_host_android/ flutter_host_ios/ flutter_module/
保持 Native 和 Flutter 工程入口在同一目錄下明肮,三個(gè)工程各自由自己的 Git 進(jìn)行版本管理柿估。
flutter_module 目錄下包含了 Flutter 開發(fā)人員編寫的 Flutter 代碼和 Flutter 的編譯產(chǎn)物秫舌。對(duì)于 Native 工程而言绣檬,他們不關(guān)心其中的 Flutter 代碼河咽,他們只需要將資源定位到其中的編譯產(chǎn)物忘蟹,然后引入項(xiàng)目即可。
Android 引入 Flutter
在工程 settings.gradle
文件中添加
//增加內(nèi)容
setBinding(new Binding([gradle: this]))
evaluate(new File( settingsDir.parentFile,'flutter_module/.android/include_flutter.groovy' ))
在工程 build.gradle
文件中添加
dependencies {
.
.
implementation project(':flutter')
}
當(dāng)然配置完 gradle 后不要忘記同步狠毯。
需要注意的是嚼松,F(xiàn)lutter 開發(fā)人員在編寫完代碼提交到 Git 之前献酗,需要執(zhí)行一次完整的 Flutter 項(xiàng)目,以便生成最新的編譯產(chǎn)物很澄,否則 Native 端在調(diào)用 Flutter 時(shí)不會(huì)執(zhí)行最新的代碼甩苛。
當(dāng)然讯蒲,結(jié)偶性更好的方案是爱葵,只給 Native 開發(fā)人員提供 Flutter 產(chǎn)物反浓,甚至使用 Cocoapods 這類包管理工具進(jìn)行自動(dòng)導(dǎo)入和版本控制雷则。大家可以根據(jù)自己的需求來實(shí)現(xiàn)具體的工程配置月劈。
Flutter & Native 交互
Flutter 和 Native 之間的交互以及數(shù)據(jù)交換傳輸猜揪,依賴于一種稱為 Platform Channel 的工具。
Platform Channel
Platform Channel 主要做了三件事:
- 發(fā)送消息
- 監(jiān)聽發(fā)送過來的消息
- 對(duì)不同平臺(tái)的數(shù)據(jù)類型進(jìn)行自動(dòng)轉(zhuǎn)換
Flutter 定義了三種不同類型的 Channel :
- BasicMessageChannel:用于傳遞字符串和半結(jié)構(gòu)化的信息腊凶。
- MethodChannel:用于傳遞方法調(diào)用(method invocation)钧萍。
- EventChannel: 用于數(shù)據(jù)流(event streams)的通信风瘦。
三種 Channel 看似復(fù)雜万搔,而且他們都有各自的使用場(chǎng)景,但他們?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)?channel 找到對(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):
StandardMessageCodec
StandardMessageCodec 是 BasicMessageChannel 的默認(rèn)編解碼器谓娃,其支持基礎(chǔ)數(shù)據(jù)類型、二進(jìn)制數(shù)據(jù)俯艰、列表竹握、字典辆飘。
BinaryCodec
BinaryCodec 是最為簡單的一種 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 作為序列化工具局骤。
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)ⅰ?/p>
由于處理的是方法調(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):
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 。
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ù)瘦穆。
消息處理器: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 方法將消息傳遞過去。
Flutter & Native 交互實(shí)操
BasicMessageChannel
Flutter
static const messageChannel = const BasicMessageChannel('samples.flutter.io/message', StandardMessageCodec());
static const messageChannel2 = const BasicMessageChannel('samples.flutter.io/message2', StandardMessageCodec());
Future<String> sendMessage() async {
String reply = await messageChannel.send('發(fā)送給Native端的數(shù)據(jù)');
print('reply: $reply'); return reply;
}
void receiveMessage() {
messageChannel2.setMessageHandler((message) async {
print('message: $message');
return '返回Native端的數(shù)據(jù)';
});
}
@override void initState() {
// TODO:
implement initState super.initState();
receiveMessage();
sendMessage();
}
iOS
// 初始化定義
FlutterBasicMessageChannel* messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"samples.flutter.io/message" binaryMessenger:controller];
// 接收消息監(jiān)聽
[messageChannel setMessageHandler:^(id message, FlutterReply callback) {
NSLog(message);
callback(@"返回flutter端的數(shù)據(jù)");
}];
// 觸發(fā)事件執(zhí)行
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterBasicMessageChannel* messageChannel2 = [FlutterBasicMessageChannel messageChannelWithName:@"samples.flutter.io/message2" binaryMessenger:controller];
// 發(fā)送消息
[messageChannel2 sendMessage:(@"發(fā)送給flutter的數(shù)據(jù)") reply:^(id reply) {
NSLog(reply);
}];
Android
BasicMessageChannel<Object> messageChannel = new BasicMessageChannel<Object>(getFlutterView(), "samples.flutter.io/message", StandardMessageCodec.INSTANCE);
// 接收消息監(jiān)聽
messageChannel.setMessageHandler(new BasicMessageChannel.MessageHandler<Object>() {
@Override
public void onMessage(Object o, BasicMessageChannel.Reply<Object> reply) {
System.out.println("onMessage: " + o);
reply.reply("返回給flutter的數(shù)據(jù)");
}
});
// 觸發(fā)事件執(zhí)行
BasicMessageChannel<Object> messageChannel2 = new BasicMessageChannel<Object>(getFlutterView(), "samples.flutter.io/message2", StandardMessageCodec.INSTANCE);
// 發(fā)送消息
messageChannel2.send("發(fā)送給flutter的數(shù)據(jù)", new BasicMessageChannel.Reply<Object>() {
@Override
public void reply(Object o) {
System.out.println("onReply: " + o);
}
});
MethodChannel
Flutter
static const platform = const MethodChannel('samples.flutter.io/battery');
Future<Null> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
// 執(zhí)行_getBatteryLevel方法
iOS
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.io/battery"
binaryMessenger:controller];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// TODO
if ([@"getBatteryLevel" isEqualToString:call.method]) {
int batteryLevel = [self getBatteryLevel];
if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery info unavailable"
details:nil]);
} else {
result(@(batteryLevel));
}
} else {
result(FlutterMethodNotImplemented);
}
}];
- (int)getBatteryLevel {
UIDevice* device = UIDevice.currentDevice;
device.batteryMonitoringEnabled = YES;
if (device.batteryState == UIDeviceBatteryStateUnknown) {
return -1;
} else {
return (int)(device.batteryLevel * 100);
}
}
Android
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// TODO
if (call.method.equals("getBatteryLevel")) {
int batteryLevel = getBatteryLevel();
if (batteryLevel != -1) {
result.success(batteryLevel);
} else {
result.error("UNAVAILABLE", "Battery level not available.", null);
}
} else {
result.notImplemented();
}
}
}
);
private int getBatteryLevel() {
int batteryLevel = -1;
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
} else {
Intent intent = new ContextWrapper(getApplicationContext()).
registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
}
return batteryLevel;
}
FlutterEventChannel
Flutter
static const EventChannel _eventChannel = const EventChannel('samples.flutter.io/test');
void _onEvent(Object event) {
print('返回的內(nèi)容: $event');
}
void _onError(Object error) {
print('返回的錯(cuò)誤');
}
@override
void initState() {
// TODO: implement initState
super.initState();
// 監(jiān)聽開始
_eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
iOS
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterEventChannel* eventChannel = [FlutterEventChannel eventChannelWithName:@"samples.flutter.io/test" binaryMessenger:controller];
[eventChannel setStreamHandler:self];
FlutterEventSink eventSink;
// // 這個(gè)onListen是Flutter端開始監(jiān)聽這個(gè)channel時(shí)的回調(diào)棋凳,第二個(gè)參數(shù) EventSink是用來傳數(shù)據(jù)的載體拍棕。
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
eventSink:(FlutterEventSink)events {
eventSink = events;
// arguments flutter給native的參數(shù)
// 回調(diào)給flutter, 建議使用實(shí)例指向,因?yàn)樵揵lock可以使用多次
if (events) {
events(@"主動(dòng)發(fā)送通知到flutter");
}
// 監(jiān)聽電池狀態(tài)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onBatteryStateDidChange:)
name:UIDeviceBatteryStateDidChangeNotification
object:nil];
return nil;
}
/// flutter不再接收
- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
// arguments flutter給native的參數(shù)
[[NSNotificationCenter defaultCenter] removeObserver:self];
eventSink = nil;
return nil;
}
- (void)onBatteryStateDidChange:(NSNotification*)notification {
if (eventSink == nil) return;
UIDeviceBatteryState state = [[UIDevice currentDevice] batteryState];
switch (state) {
case UIDeviceBatteryStateFull:
case UIDeviceBatteryStateCharging:
eventSink(@"charging");
break;
case UIDeviceBatteryStateUnplugged:
eventSink(@"discharging");
break;
default:
eventSink([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Charging status unavailable"
details:nil]);
break;
}
}
Android
new EventChannel(getFlutterView(), CHANNEL2).setStreamHandler(
new EventChannel.StreamHandler() {
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
this.eventSink = eventSink;
handler.sendEmptyMessageDelayed(1, 1000);
}
@Override
public void onCancel(Object o) {
}
private EventChannel.EventSink eventSink;
private int count = 0;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
eventSink.success((count++) + "主動(dòng)發(fā)送消息給flutter");
// handler.sendEmptyMessageDelayed(1,1000);
}
};
}
);