前言
我們都知道Flutter開發(fā)的app是可以同時(shí)在iOS和Android系統(tǒng)上運(yùn)行的邦危。顯然Flutter需要有和Native通信的能力毯侦。比如說窄瘟,你的Flutter app要顯示手機(jī)的電量米死,而電量只能通過平臺的系統(tǒng)Api獲取锌历。這時(shí)就需要有個(gè)機(jī)制使得Flutter可以通過某種方式來調(diào)用這個(gè)系統(tǒng)Api并且獲得返回值。那么Flutter是如何做到的呢峦筒?答案是Platform Channels究西。
Platform Channels
先來看張圖
上圖來自Flutter官網(wǎng),表明了Platform Channels的架構(gòu)示意圖物喷。有細(xì)心的同學(xué)就要問了卤材,你不是說Flutter和Native通信是通過Platform Channels嗎?怎么架構(gòu)圖里面連接他們的是MethodChannel? 其實(shí)呢峦失,MethodChannel是Platform Channels中的一種扇丛,顧名思義,MethodChannel用起來應(yīng)該和方法調(diào)用差不多尉辑。那么還有別的channel帆精?有的,還有EventChannel,BasicMessageChannel等卓练。如果你需要把數(shù)據(jù)從Native平臺發(fā)送給Flutter隘蝎,推薦你使用EventChannel。Flutter framework也是在用這些通道和Native通信昆庇,具體可以參考一下
FlutterView.java
末贾,在這里能看到Platform Channels的更多用法。
這里需要注意一點(diǎn)整吆,為了保證UI的響應(yīng)拱撵,通過Platform Channels傳遞的消息都是異步的。
在Platform Channels上傳遞的消息都是經(jīng)過編碼的表蝙,編碼的方式也有幾種拴测,默認(rèn)的是用StandardMethodCodec
。其他的還有BinaryCodec
(二進(jìn)制的編碼府蛇,其實(shí)啥也沒干集索,直接把入?yún)⒔o返回了), JSONMessageCodec
(JSON格式的編碼),StringCodec
(String格式的編碼)汇跨。這些編解碼器允許的只能是以下這些類型:
所以如果你想把你自己定義的
com.yourmodule.YourObject
類型的一個(gè)實(shí)例直接扔給Platform Channels傳送是不行滴务荆。
Platform Channels 怎么用
前面大概介紹了Flutter和Native通信的Platform Channels。那么我們用具體的例子來說說Platform Channels的使用穷遂。這里使用Flutter官方出的獲取手機(jī)電量的Demo函匕。相關(guān)源代碼可以從Github下載。
Platform Channels是連接Flutter和Native的通道蚪黑,那么我們?nèi)绻⑦@樣的通道顯然要在兩端都要寫代碼嘍盅惜。
MethodChannel
先看Native 端怎么寫
MethodChannel-Native 端
為簡單起見,本例的Android端代碼都直接寫在MainActivity
中忌穿。Android平臺下獲取電量是通過調(diào)用BatteryManager來獲取的抒寂,所以我們先在MainActivity
中增加一個(gè)獲取電量的函數(shù):
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;
}
這個(gè)函數(shù)需要能被Flutter app調(diào)用,此時(shí)就需要通過MethodChannel
來建立這個(gè)通道了掠剑。
首先在MainActivity
的onCreate
函數(shù)中加入以下代碼來新建一個(gè)MethodChannel
public class MainActivity extends FlutterActivity {
//channel的名稱屈芜,由于app中可能會有多個(gè)channel,這個(gè)名稱需要在app內(nèi)是唯一的朴译。
private static final String CHANNEL = "samples.flutter.io/battery";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
// 直接 new MethodChannel沸伏,然后設(shè)置一個(gè)Callback來處理Flutter端調(diào)用
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// 在這個(gè)回調(diào)里處理從Flutter來的調(diào)用
}
});
}
}
注意,每個(gè)
MethodChannel
需要有唯一的字符串作為標(biāo)識动分,用以互相區(qū)分,這個(gè)名稱建議使用package.module...
這樣的模式來命名红选。因?yàn)樗械?code>MethodChannel都是保存在以通道名為Key的Map中澜公。所以你要是設(shè)了兩個(gè)名字一樣的channel,只有后設(shè)置的那個(gè)會生效。
接下來我們來填充onMethodCall
坟乾。
@Override
public void onMethodCall(MethodCall call, Result result) {
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();
}
}
onMethodCall
有兩個(gè)入?yún)ⅲ?code>MethodCall里包含要調(diào)用的方法名稱和參數(shù)迹辐。Result
是給Flutter的返回值。方法名是兩端協(xié)商好的甚侣。通過if語句判斷MethodCall.method
來區(qū)分不同的方法明吩,在我們的例子里面我們只會處理名為“getBatteryLevel”的調(diào)用。在調(diào)用本地方法獲取到電量以后通過result.success(batteryLevel)
調(diào)用把電量值返回給Flutter殷费。
Native端的代碼就完成了印荔。是不是很簡單?
MethodChannel-Flutter 端
接下來看Flutter端代碼怎么寫:
首先在 State
中創(chuàng)建Flutter端的MethodChannel
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.io/battery');
// Get battery level.
}
channel的名稱要和Native端的一致详羡。
然后是通過MethodChannel調(diào)用的代碼
String _batteryLevel = 'Unknown battery level.';
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;
});
}
final int result = await platform.invokeMethod('getBatteryLevel');
這行代碼就是通過通道來調(diào)用Native方法了仍律。注意這里的await
關(guān)鍵字。前面我們說過MethodChannel是異步的实柠,所以這里必須要使用await
關(guān)鍵字。
在上面Native代碼中我們把獲取到的電量通過result.success(batteryLevel);
返回給Flutter窒盐。這里await
表達(dá)式執(zhí)行完成以后電量就直接賦值給result
變量了草则。剩下的就是怎么展示的問題了,就不再細(xì)說了蟹漓,具體可以去看代碼炕横。
需要注意的是,這里我們只介紹了從Flutter調(diào)用Native方法牧牢,其實(shí)通過MethodChannel
看锉,Native也能調(diào)用Flutter的方法,這是一個(gè)雙向的通道塔鳍。
舉個(gè)例子伯铣,我們想從Native端請求Flutter端的一個(gè)getName
方法獲取一個(gè)字符串。在Flutter端你需要給MethodChannel
設(shè)置一個(gè)MethodCallHandler
_channel.setMethodCallHandler(platformCallHandler);
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "getName":
return "Hello from Flutter";
break;
}
}
在Native端轮纫,只需要讓對應(yīng)的的channel調(diào)用invokeMethod
就行了
channel.invokeMethod("getName", null, new MethodChannel.Result() {
@Override
public void success(Object o) {
// 這里就會輸出 "Hello from Flutter"
Log.i("debug", o.toString());
}
@Override
public void error(String s, String s1, Object o) {
}
@Override
public void notImplemented() {
}
});
至此腔寡,MethodChannel
的用法就介紹完了≌仆伲可以發(fā)現(xiàn)放前,通過MethodChannel
Native和Flutter方法互相調(diào)用還是蠻直接的。這里只是做了個(gè)大概的介紹糯彬,具體細(xì)節(jié)和一些復(fù)雜用法還有待大家的探索凭语。
MethodChannel
提供了方法調(diào)用的通道,那如果Native有數(shù)據(jù)流需要傳送給Flutter該怎么辦呢撩扒?這時(shí)候就要用到EventChannel
了似扔。
EventChannel
EventChannel
的使用我們也以官方獲取電池電量的demo為例,手機(jī)的電池狀態(tài)是不停變化的。我們要把這樣的電池狀態(tài)變化由Native及時(shí)通過EventChannel
來告訴Flutter炒辉。這種情況用之前講的MethodChannel
辦法是不行的豪墅,這意味著Flutter需要用輪詢的方式不停調(diào)用getBatteryLevel
來獲取當(dāng)前電量,顯然是不正確的做法黔寇。而用EventChannel
的方式偶器,則是將當(dāng)前電池狀態(tài)"推送"給Flutter.
EventChannel - Native端
先看我們熟悉的Native端怎么來創(chuàng)建EventChannel
, 還是在MainActivity.onCreate
中,我們加入如下代碼:
new EventChannel(getFlutterView(), "samples.flutter.io/charging").setStreamHandler(
new StreamHandler() {
// 接收電池廣播的BroadcastReceiver缝裤。
private BroadcastReceiver chargingStateChangeReceiver;
@Override
// 這個(gè)onListen是Flutter端開始監(jiān)聽這個(gè)channel時(shí)的回調(diào)屏轰,第二個(gè)參數(shù) EventSink是用來傳數(shù)據(jù)的載體。
public void onListen(Object arguments, EventSink events) {
chargingStateChangeReceiver = createChargingStateChangeReceiver(events);
registerReceiver(
chargingStateChangeReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
@Override
public void onCancel(Object arguments) {
// 對面不再接收
unregisterReceiver(chargingStateChangeReceiver);
chargingStateChangeReceiver = null;
}
}
);
和MethodChannel
類似倘是,我們也是直接new一個(gè)EventChannel
實(shí)例亭枷,并給它設(shè)置了一個(gè)StreamHandler
類型的回調(diào)。其中onCancel
代表對面不再接收搀崭,這里我們應(yīng)該做一些clean up的事情叨粘。而 onListen
則代表通道已經(jīng)建好,Native可以發(fā)送數(shù)據(jù)了瘤睹。注意onListen
里帶的EventSink
這個(gè)參數(shù)升敲,后續(xù)Native發(fā)送數(shù)據(jù)都是經(jīng)過EventSink
的『浯看代碼:
private BroadcastReceiver createChargingStateChangeReceiver(final EventSink events) {
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) {
events.error("UNAVAILABLE", "Charging status unavailable", null);
} else {
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
// 把電池狀態(tài)發(fā)給Flutter
events.success(isCharging ? "charging" : "discharging");
}
}
};
}
在onReceive
函數(shù)內(nèi)驴党,系統(tǒng)發(fā)來電池狀態(tài)廣播以后,在Native這里轉(zhuǎn)化為約定好的字符串获茬,然后通過調(diào)用events.success();
發(fā)送給Flutter港庄。Native端的代碼就是這樣,接下來看Flutter端恕曲。
EventChannel - Flutter端
首先還是在State內(nèi)創(chuàng)建EventChannel
static const EventChannel eventChannel =
const EventChannel('samples.flutter.io/charging');
然后在initState
的時(shí)候打開這個(gè)channel:
@override
void initState() {
super.initState();
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
收到event以后的處理是在_onEvent
函數(shù)里:
void _onEvent(Object event) {
setState(() {
_chargingStatus =
"Battery status: ${event == 'charging' ? '' : 'dis'}charging.";
});
}
void _onError(Object error) {
setState(() {
_chargingStatus = 'Battery status: unknown.';
});
}
從Native端傳過來的"charging"/"discharging"字符串直接就是入?yún)?code>event鹏氧。好了,F(xiàn)lutter端的代碼也貼完了佩谣,是不是感覺EventChannel
用起來也很簡單把还?
收尾
至此,本文對Flutter和Native之間互相通信的方式的講解也要告一段落了茸俭。Flutter的出發(fā)點(diǎn)就是跨平臺吊履,而真正要做到跨平臺則取決于Flutter是否能通過簡單的方式與Native高效通信。Platform Channels能否實(shí)現(xiàn)這個(gè)目標(biāo)還有待大規(guī)模應(yīng)用的檢驗(yàn)调鬓。對于Flutter開發(fā)者來講艇炎,由于眾多的Native平臺API需要暴露給Flutter,還有很多用Native實(shí)現(xiàn)的組件/業(yè)務(wù)邏輯也可能需要暴露給Flutter腾窝。這需要寫大量的通道代碼冕臭,也就是說我們必須掌握使用Platform Channels的技能腺晾,才能體會到Flutter真正的跨平臺能力。本文中對Platform Channels的應(yīng)用只是非常簡單的demo辜贵。在大型app中還存在兩大挑戰(zhàn),一個(gè)是大量的通道我們?nèi)绾谓M織归形,如何維護(hù)托慨。另一個(gè)是通道協(xié)議如何設(shè)計(jì)才能抹平Android和iOS之間的平臺差異,這就需要開發(fā)這對兩個(gè)平臺都非常熟悉暇榴,這個(gè)貌似更加困難厚棵。
當(dāng)然了,如果你做出來了完美的通道蔼紧,將平臺的某個(gè)功能(比如藍(lán)牙婆硬,GPS什么的)包裝成了優(yōu)美的Flutter API,并且希望世界上其他Flutter開發(fā)者也能使用奸例。那么你可以把你智慧的結(jié)晶通過發(fā)布Flutter插件(plugin)的方式開放給別人彬犯。下篇文章我會介紹一下如何來開發(fā)一個(gè)Flutter插件,敬請期待查吊。