咱們都知道Flutter開發的app是能夠同時在iOS和Android系統上運行的。顯然Flutter須要有和Native通訊的能力。好比說,你的Flutter app要顯示手機的電量,而電量只能經過平臺的系統Api獲取。這時就須要有個機制使得Flutter能夠經過某種方式來調用這個系統Api而且得到返回值。那麼Flutter是如何作到的呢?答案是Platform Channels。java
先來看張圖 git
上圖來自Flutter官網,代表了Platform Channels的架構示意圖。有細心的同窗就要問了,你不是說Flutter和Native通訊是經過Platform Channels嗎?怎麼架構圖裏面鏈接他們的是MethodChannel? 其實呢,MethodChannel是Platform Channels中的一種,顧名思義,MethodChannel用起來應該和方法調用差很少。那麼還有別的channel?有的,還有EventChannel,BasicMessageChannel等。若是你須要把數據從Native平臺發送給Flutter,推薦你使用EventChannel。Flutter framework也是在用這些通道和Native通訊,具體能夠參考一下FlutterView.java
,在這裏能看到Platform Channels的更多用法。
這裏須要注意一點,爲了保證UI的響應,經過Platform Channels傳遞的消息都是異步的。github
在Platform Channels上傳遞的消息都是通過編碼的,編碼的方式也有幾種,默認的是用StandardMethodCodec
。其餘的還有BinaryCodec
(二進制的編碼,其實啥也沒幹,直接把入參給返回了), JSONMessageCodec
(JSON格式的編碼),StringCodec
(String格式的編碼)。這些編解碼器容許的只能是如下這些類型:bash
com.yourmodule.YourObject
類型的一個實例直接扔給Platform Channels傳送是不行滴。
前面大概介紹了Flutter和Native通訊的Platform Channels。那麼咱們用具體的例子來講說Platform Channels的使用。這裏使用Flutter官方出的獲取手機電量的Demo。相關源代碼能夠從Github下載。架構
Platform Channels是鏈接Flutter和Native的通道,那麼咱們若是要創建這樣的通道顯然要在兩端都要寫代碼嘍。app
先看Native 端怎麼寫異步
爲簡單起見,本例的Android端代碼都直接寫在MainActivity
中。Android平臺下獲取電量是經過調用BatteryManager來獲取的,因此咱們先在MainActivity
中增長一個獲取電量的函數:async
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;
}
複製代碼
這個函數須要能被Flutter app調用,此時就須要經過MethodChannel
來創建這個通道了。 首先在MainActivity
的onCreate
函數中加入如下代碼來新建一個MethodChannel
ide
public class MainActivity extends FlutterActivity {
//channel的名稱,因爲app中可能會有多個channel,這個名稱須要在app內是惟一的。
private static final String CHANNEL = "samples.flutter.io/battery";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
// 直接 new MethodChannel,而後設置一個Callback來處理Flutter端調用
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// 在這個回調裏處理從Flutter來的調用
}
});
}
}
複製代碼
注意,每一個
MethodChannel
須要有惟一的字符串做爲標識,用以互相區分,這個名稱建議使用package.module...
這樣的模式來命名。由於全部的MethodChannel
都是保存在以通道名爲Key的Map中。因此你要是設了兩個名字同樣的channel,只有後設置的那個會生效。函數
接下來咱們來填充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
有兩個入參,MethodCall
裏包含要調用的方法名稱和參數。Result
是給Flutter的返回值。方法名是兩端協商好的。經過if語句判斷MethodCall.method
來區分不一樣的方法,在咱們的例子裏面咱們只會處理名爲「getBatteryLevel」的調用。在調用本地方法獲取到電量之後經過result.success(batteryLevel)
調用把電量值返回給Flutter。 Native端的代碼就完成了。是否是很簡單?
接下來看Flutter端代碼怎麼寫: 首先在 State
中建立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調用的代碼
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');
這行代碼就是經過通道來調用Native方法了。注意這裏的await
關鍵字。前面咱們說過MethodChannel是異步的,因此這裏必需要使用await
關鍵字。 在上面Native代碼中咱們把獲取到的電量經過result.success(batteryLevel);
返回給Flutter。這裏await
表達式執行完成之後電量就直接賦值給result
變量了。剩下的就是怎麼展現的問題了,就再也不細說了,具體能夠去看代碼。
須要注意的是,這裏咱們只介紹了從Flutter調用Native方法,其實經過MethodChannel
,Native也能調用Flutter的方法,這是一個雙向的通道。
舉個例子,咱們想從Native端請求Flutter端的一個getName
方法獲取一個字符串。在Flutter端你須要給MethodChannel
設置一個MethodCallHandler
_channel.setMethodCallHandler(platformCallHandler);
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "getName":
return "Hello from Flutter";
break;
}
}
複製代碼
在Native端,只須要讓對應的的channel調用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
的用法就介紹完了。能夠發現,經過MethodChannel
Native和Flutter方法互相調用仍是蠻直接的。這裏只是作了個大概的介紹,具體細節和一些複雜用法還有待你們的探索。
MethodChannel
提供了方法調用的通道,那若是Native有數據流須要傳送給Flutter該怎麼辦呢?這時候就要用到EventChannel
了。
EventChannel
的使用咱們也以官方獲取電池電量的demo爲例,手機的電池狀態是不停變化的。咱們要把這樣的電池狀態變化由Native及時經過EventChannel
來告訴Flutter。這種狀況用以前講的MethodChannel
辦法是不行的,這意味着Flutter須要用輪詢的方式不停調用getBatteryLevel
來獲取當前電量,顯然是不正確的作法。而用EventChannel
的方式,則是將當前電池狀態"推送"給Flutter.
先看咱們熟悉的Native端怎麼來建立EventChannel
, 仍是在MainActivity.onCreate
中,咱們加入以下代碼:
new EventChannel(getFlutterView(), "samples.flutter.io/charging").setStreamHandler(
new StreamHandler() {
// 接收電池廣播的BroadcastReceiver。
private BroadcastReceiver chargingStateChangeReceiver;
@Override
// 這個onListen是Flutter端開始監聽這個channel時的回調,第二個參數 EventSink是用來傳數據的載體。
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一個EventChannel
實例,並給它設置了一個StreamHandler
類型的回調。其中onCancel
表明對面再也不接收,這裏咱們應該作一些clean up的事情。而 onListen
則表明通道已經建好,Native能夠發送數據了。注意onListen
裏帶的EventSink
這個參數,後續Native發送數據都是通過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;
// 把電池狀態發給Flutter
events.success(isCharging ? "charging" : "discharging");
}
}
};
}
複製代碼
在onReceive
函數內,系統發來電池狀態廣播之後,在Native這裏轉化爲約定好的字符串,而後經過調用events.success();
發送給Flutter。Native端的代碼就是這樣,接下來看Flutter端。
首先仍是在State內建立EventChannel
static const EventChannel eventChannel =
const EventChannel('samples.flutter.io/charging');
複製代碼
而後在initState
的時候打開這個channel:
@override
void initState() {
super.initState();
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
複製代碼
收到event之後的處理是在_onEvent
函數裏:
void _onEvent(Object event) {
setState(() {
_chargingStatus =
"Battery status: ${event == 'charging' ? '' : 'dis'}charging.";
});
}
void _onError(Object error) {
setState(() {
_chargingStatus = 'Battery status: unknown.';
});
}
複製代碼
從Native端傳過來的"charging"/"discharging"字符串直接就是入參event
。好了,Flutter端的代碼也貼完了,是否是感受EventChannel
用起來也很簡單?
至此,本文對Flutter和Native之間互相通訊的方式的講解也要告一段落了。Flutter的出發點就是跨平臺,而真正要作到跨平臺則取決於Flutter是否能經過簡單的方式與Native高效通訊。Platform Channels可否實現這個目標還有待大規模應用的檢驗。對於Flutter開發者來說,因爲衆多的Native平臺API須要暴露給Flutter,還有不少用Native實現的組件/業務邏輯也可能須要暴露給Flutter。這須要寫大量的通道代碼,也就是說咱們必須掌握使用Platform Channels的技能,才能體會到Flutter真正的跨平臺能力。本文中對Platform Channels的應用只是很是簡單的demo。在大型app中還存在兩大挑戰,一個是大量的通道咱們如何組織,如何維護。另外一個是通道協議如何設計才能抹平Android和iOS之間的平臺差別,這就須要開發這對兩個平臺都很是熟悉,這個貌似更加困難。
固然了,若是你作出來了完美的通道,將平臺的某個功能(好比藍牙,GPS什麼的)包裝成了優美的Flutter API,而且但願世界上其餘Flutter開發者也能使用。那麼你能夠把你智慧的結晶經過發佈Flutter插件(plugin)的方式開放給別人。下篇文章我會介紹一下如何來開發一個Flutter插件,敬請期待。