前言
我們都知道Flutter開發(fā)的app是可以同時(shí)在iOS和Android系統(tǒng)上運(yùn)行的。顯然Flutter需要有和Native通信的能力。比如說,你的Flutter app要顯示手機(jī)的電量,而電量只能通過平臺(tái)的系統(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平臺(tái)發(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平臺(tái)下獲取電量是通過調(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中可能會(huì)有多個(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)識(shí),用以互相區(qū)分,這個(gè)名稱建議使用package.module...這樣的模式來命名。因?yàn)樗械?code>MethodChannel都是保存在以通道名為Key的Map中。所以你要是設(shè)了兩個(gè)名字一樣的channel,只有后設(shè)置的那個(gè)會(huì)生效。
接下來我們來填充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ū)分不同的方法,在我們的例子里面我們只會(huì)處理名為“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端請(qǐng)求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端,只需要讓對(duì)應(yīng)的的channel調(diào)用invokeMethod就行了
channel.invokeMethod("getName", null, new MethodChannel.Result() {
@Override
public void success(Object o) {
// 這里就會(huì)輸出 "Hello from Flutter"
Log.i("debug", o.toString());
}
@Override
public void error(String s, String s1, Object o) {
}
@Override
public void notImplemented() {
}
});
至此,MethodChannel的用法就介紹完了??梢园l(fā)現(xiàn),通過MethodChannelNative和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) {
// 對(duì)面不再接收
unregisterReceiver(chargingStateChangeReceiver);
chargingStateChangeReceiver = null;
}
}
);
和MethodChannel類似,我們也是直接new一個(gè)EventChannel實(shí)例,并給它設(shè)置了一個(gè)StreamHandler類型的回調(diào)。其中onCancel代表對(duì)面不再接收,這里我們應(yīng)該做一些clean up的事情。而 onListen則代表通道已經(jīng)建好,Native可以發(fā)送數(shù)據(jù)了。注意onListen里帶的EventSink這個(gè)參數(shù),后續(xù)Native發(fā)送數(shù)據(jù)都是經(jīng)過EventSink的??创a:
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用起來也很簡單?
收尾
至此,本文對(duì)Flutter和Native之間互相通信的方式的講解也要告一段落了。Flutter的出發(fā)點(diǎn)就是跨平臺(tái),而真正要做到跨平臺(tái)則取決于Flutter是否能通過簡單的方式與Native高效通信。Platform Channels能否實(shí)現(xiàn)這個(gè)目標(biāo)還有待大規(guī)模應(yīng)用的檢驗(yàn)。對(duì)于Flutter開發(fā)者來講,由于眾多的Native平臺(tái)API需要暴露給Flutter,還有很多用Native實(shí)現(xiàn)的組件/業(yè)務(wù)邏輯也可能需要暴露給Flutter。這需要寫大量的通道代碼,也就是說我們必須掌握使用Platform Channels的技能,才能體會(huì)到Flutter真正的跨平臺(tái)能力。本文中對(duì)Platform Channels的應(yīng)用只是非常簡單的demo。在大型app中還存在兩大挑戰(zhàn),一個(gè)是大量的通道我們?nèi)绾谓M織,如何維護(hù)。另一個(gè)是通道協(xié)議如何設(shè)計(jì)才能抹平Android和iOS之間的平臺(tái)差異,這就需要開發(fā)這對(duì)兩個(gè)平臺(tái)都非常熟悉,這個(gè)貌似更加困難。
當(dāng)然了,如果你做出來了完美的通道,將平臺(tái)的某個(gè)功能(比如藍(lán)牙,GPS什么的)包裝成了優(yōu)美的Flutter API,并且希望世界上其他Flutter開發(fā)者也能使用。那么你可以把你智慧的結(jié)晶通過發(fā)布Flutter插件(plugin)的方式開放給別人。下篇文章我會(huì)介紹一下如何來開發(fā)一個(gè)Flutter插件,敬請(qǐng)期待。