本文首發於微信公眾號「Android開發之旅」,歡迎關注 ,獲取更多技術乾貨
通訊場景
我們在做Flutter混合開發的時候通常需要進行Flutter和Native之間的通訊。 比如Dart呼叫Native的相簿選擇圖片,Native將電量、GPS資訊主動傳遞給Dart等等。在混合開發中通訊通常有以下幾種:
- 初始化Flutter時Native向Dart傳遞資料。
- Native傳送資料給Dart。
- Dart傳送資料給Native。
- Dart傳送資料給Native,然後Native回傳資料給Dart。
第一種通訊方式我們在講解原生專案接入Flutter時已經講解過,有興趣的同學可以移步到Flutter混合開發(一):Android專案整合Flutter模組詳細指南看下。
通訊機制
Flutter與Native端之間的通訊機制是通過Platform Channel來完成。訊息使用Channel在Flutter端和Native端進行傳遞。具體如下圖所示:
從圖中可以看出,兩端之間的通訊都是雙向的,而且是完成非同步傳遞。Flutter定義了三種不同型別的Channel:
- BasicMessageChannel:用於傳遞字串或者半結構化的資訊,持續通訊,收到資訊後可以進行回覆。
- MethodChannel:用於傳遞方法呼叫,一次性通訊。通常用於Dart呼叫Native的方法。
- EventChannel:用於資料流的通訊,持續通訊,收到訊息後無法回覆此次訊息。通常用於Native向Dart的通訊。
下面我們就來看看這三種Channel通訊方式的具體使用和介紹。
BasicMessageChannel
Android端的相關方法:
BasicMessageChannel(BinaryMessenger messenger, String name, MessageCodec<T> codec)
複製程式碼
- messenger引數是訊息信使(FlutterView),是訊息傳送和接受的工具。
- name引數是channel的名字也是其唯一標識,要和Dart端統一。
- codec是訊息編解碼器,也需要和Dart端統一。它的作用就是將訊息在傳送的時候進行加密,dart端收到訊息後在進行解密,傳遞的都是二進位制資料。它有四種型別:BinaryCodec、StringCodec、JSONMessageCodec、StandardMessageCodec,四種型別均屬於MessageCodec範疇。如不指定預設是StandardMessageCodec。
當我們需要接受來自Dart端傳送的訊息時使用setMessageHandler方法:
void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler)
複製程式碼
引數handler是訊息處理器,配合BinaryMessenger來完成對訊息的處理。它是一個介面,具體的實現在onMessage方法中:
public interface MessageHandler<T> {
void onMessage(T message, BasicMessageChannel.Reply<T> reply);
}
複製程式碼
引數message即為Dart傳送的資料, reply是用於回覆訊息的回撥函式,提供reply.reply("")設定回覆的內容。
上面講的是接受Dart端的訊息,那麼Native端主動傳送訊息則是使用send方法,它有兩個過載方法:
void send(T message)
void send(T message, BasicMessageChannel.Reply<T> callback)
複製程式碼
引數message即為要發生給Dart的資料,callback回撥則是用於接收Dart端收到訊息後的回覆資訊。
Dart端相關方法:
const BasicMessageChannel(this.name, this.codec);
複製程式碼
這裡的name和codec和Android端的構造方法引數是一樣的,那麼是channel的名字也是唯一標識,codec是訊息編解碼器,兩個引數在兩端必須統一。
Dart端如果要接受Native端的訊息則要設定setMessageHandler方法:
void setMessageHandler(Future<T> handler(T message))
複製程式碼
引數handler為訊息處理器,配合BinaryMessenger來完成對訊息的處理。
通過send方法向Native端傳送訊息:
Future<T> send(T message)
複製程式碼
引數message為要傳遞的引數。Future為傳送訊息後等待Native回覆的回撥函式。
BasicMessageChannel實戰:Android端和Flutter端相互傳送訊息,並且在收到訊息後返回對方資訊
Android端程式碼:
//初始化BasicMessageChannel
BasicMessageChannel<String> basicMessageChannel = new BasicMessageChannel<>(flutterView,
"BasicMessageChannelPlugin",StringCodec.INSTANCE);
//接受訊息
basicMessageChannel.setMessageHandler((message, reply) -> {
mTvDart.setText(message);
reply.reply("收到dart資料:接受成功");
});
//傳送訊息
basicMessageChannel.send(message, reply -> mTvDart.setText(reply));
複製程式碼
Dart端程式碼:
//初始化BasicMessageChannel
static const BasicMessageChannel<String> _basicMessageChannel =
BasicMessageChannel("BasicMessageChannelPlugin", StringCodec());
// 接受訊息
void handleBasicMessageChannel() {
_basicMessageChannel
.setMessageHandler((String message) => Future<String>(() {
setState(() {
showMessage = message;
});
return "收到Native的訊息:接受成功";
}));
}
//傳送訊息
response = await _basicMessageChannel.send(_controller.text);
setState(() {
showMessage = response;
});
複製程式碼
最後效果為下圖,紅色分割線上部分為Native頁面,下部分為Flutter頁面。
MethodChannel
使用MethodChannel相關方法的引數型別及含義和BasicMessageChannel的引數含義都是相同的,下面就不一一解釋了。
Androd端相關方法:
MethodChannel(BinaryMessenger messenger, String name)
MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)
複製程式碼
第一個建構函式會構造一個StandardMethodCodec.INSTANCE型別的MethodCodec。MethodCodec定義了兩種型別:JSONMethodCodec和StandardMethodCodec。
如果想接受來自Dart端的訊息則使用:
setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler)
複製程式碼
MethodCallHandler為介面,回撥方法為:
public interface MethodCallHandler {
void onMethodCall(MethodCall call, MethodChannel.Result result);
}
複製程式碼
call引數有兩個成員變數,String型別的call.method表示呼叫的方法名,Object型別的call.arguments表示呼叫方法所傳遞的入參。result是回覆此訊息的回撥函式提供了result.success,result.error,result.notImplemented方法呼叫。
傳送訊息主動呼叫Dart程式碼則使用invokeMethod方法
invokeMethod(@NonNull String method, @Nullable Object arguments)
invokeMethod(String method, @Nullable Object arguments, Result callback)
複製程式碼
第二個方法多了一個callback,它是用來接受Dart端收到訊息後的回覆資訊。
public interface Result {
void success(@Nullable Object result);
void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);
void notImplemented();
}
複製程式碼
Dart端相關方法:
const MethodChannel(this.name, [this.codec = const StandardMethodCodec()])
複製程式碼
建構函式預設是使用StandardMethodCodec編解碼器。
通過setMethodCallHandler方法接受來自Native的方法呼叫,通過invokeMethod方法呼叫Native端的方法。
void setMethodCallHandler(Future<dynamic> handler(MethodCall call))
複製程式碼
Future<T> invokeMethod<T>(String method, [ dynamic arguments ])
複製程式碼
MethodChannel實戰: Native端呼叫Dart端的getPlatform方法返回當前的os平臺,Dart端呼叫Native端的getBatteryLevel方法獲取當前手機電量。
Android端程式碼:
//初始化MethodChannel
MethodChannel methodChannel = new MethodChannel(flutterView, "MethodChannelPlugin");
mBtnTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//呼叫dart端getPlatform方法
methodChannel.invokeMethod("getPlatform", null, new MethodChannel.Result() {
@Override
public void success(@Nullable Object result) {
mTvDart.setText(result.toString());
}
@Override
public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
mTvDart.setText(errorCode + "==" + errorMessage);
}
@Override
public void notImplemented() {
mTvDart.setText("未實現getPlatform方法");
}
});
}
});
//接受dart的呼叫
methodChannel.setMethodCallHandler((call, result) -> {
switch (call.method) {
case "getBatteryLevel":
int batteryLevel = getBatteryLevel();
if (batteryLevel != -1) {
result.success("電量為:" + batteryLevel);
} else {
result.error("1001", "呼叫錯誤", null);
}
break;
default:
result.notImplemented();
break;
}
});
複製程式碼
Dart端程式碼:
// receive
void handleMethodChannelReceive() {
Future<dynamic> platformCallHandler(MethodCall call) async {
switch (call.method) {
case "getPlatform":
return getPlatformName(); //呼叫success方法
// return PlatformException(code: '1002',message: "出現異常"); //呼叫error
break;
}
}
_methodChannel.setMethodCallHandler(platformCallHandler);
// _methodChannel.setMethodCallHandler(null); //呼叫notImplemented
}
//send
void handleMethodChannelSend() async {
try {
response = await _methodChannel.invokeMethod("getBatteryLevel");
print(response);
setState(() {
showMessage = response;
});
} catch (e) {
//捕獲error和notImplemented異常
setState(() {
showMessage = e.message;
});
}
}
複製程式碼
當我們在使用setMethodCallHandler接受到native的訊息時,直接呼叫相關方法即可呼叫Native端的success回撥。
如果直接拋異常如PlatformException,那麼就呼叫Native端的error回撥。
PlatformException(code: '1002',message: "出現異常")
複製程式碼
如果我們直接設定handler為null
_methodChannel.setMethodCallHandler(null);
複製程式碼
那麼就會呼叫Native端的notImplemented方法回撥。
同理我們在Dart端使用invokeMethod方法是,需要進行異常捕獲以便於我們接受到Native端呼叫的error和notImplemented方法回撥。
最後效果為下圖,紅色分割線上部分為Native頁面,下部分為Flutter頁面。
EventChannel
EventChannel內部實現原理其實也是通過MethodChannel來完成的。
Android端相關程式碼:
EventChannel(BinaryMessenger messenger, String name)
EventChannel(BinaryMessenger messenger, String name, MethodCodec codec)
複製程式碼
同樣的,也是兩個構造,預設codec為StandardMethodCodec,EventChannel和MethodChannel的codec都屬於MethodCodec範疇。
通過setStreamHandler來監聽Dart端傳送的訊息,
void setStreamHandler(EventChannel.StreamHandler handler)
複製程式碼
其中handler是一個介面:
public interface StreamHandler {
void onListen(Object args, EventChannel.EventSink eventSink);
void onCancel(Object o);
}
複製程式碼
args為dart端初始化監聽流的引數,eventSink設定了三個回撥,分別是success、error和endofStream。分別對應Dart端的ondata、error和onDone回撥。
Dart端相關程式碼:
const EventChannel(this.name, [this.codec = const StandardMethodCodec()]);
複製程式碼
通過EventChannel初始化一個channel物件。如果從Native中接受資料需要定義一個廣播流:
Stream<dynamic> receiveBroadcastStream([ dynamic arguments ])
複製程式碼
通過呼叫Stream的listen方法來完成註冊。
EventChannel實戰:Native端主動傳送電量資訊給Dart端,Dart端收到資訊後進行展示。
Android端程式碼:
EventChannel eventChannel = new EventChannel(flutterView, "EventChannelPlugin");
eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
events.success(arguments.toString() + getBatteryLevel());
//events.error("111","出現錯誤","");
//events.endOfStream();
}
@Override
public void onCancel(Object arguments) {
}
});
複製程式碼
Dart端程式碼:
//init
static const EventChannel _eventChannel = EventChannel("EventChannelPlugin");
//receive
void handleEventChannelReceive() {
streamSubscription = _eventChannel
.receiveBroadcastStream() //可以攜帶引數
.listen(_onData, onError: _onError, onDone: _onDone);
}
void _onDone() {
setState(() {
showMessage = "endOfStream";
});
}
_onError(error) {
setState(() {
PlatformException platformException = error;
showMessage = platformException.message;
});
}
void _onData(message) {
setState(() {
showMessage = message;
});
}
複製程式碼
通過event.success方法傳送資訊,dart端通過監聽Stream流來獲取資訊。當Native端呼叫events.error時在Dart端的onError回撥中需要將error轉換為PlatformException才能獲取到異常的相關資訊。
最後效果為下圖,紅色分割線上部分為Native頁面,下部分為Flutter頁面。
總結
主要是講解了Android端和Dart的三種通訊方式。詳細分析了方法構成和具體的例項使用。每一種方式都對應不同的使用場景,大家可以按需選擇,多加練習做到熟能生巧。
文中都是貼的一些程式碼片段,全部Demo原始碼已經上傳到後臺,關注公眾號回覆「混合開發」即可獲取下載連結。
推薦閱讀
Flutter混合開發(一):Android專案整合Flutter模組詳細指南
Flutter混合開發(二):iOS專案整合Flutter模組詳細指南