在不得不看的Flutter與Android混合開發一文中講述了Android項目如何導入flutter模塊,但有一個問題,就是它們之間還不能互相通訊,但這又是很是必要的。因此本文就來說述一下Android如何與flutter進行通訊。html
消息經過平臺通道在native(host)與flutter(client)之間傳遞,以下圖所示: java
爲了確保用戶界面可以正確響應,消息都是以異步的方式進行傳遞。不管是native向flutter發送消息,仍是flutter向native發送消息。segmentfault
在flutter中,MethodChannel能夠發送與方法調用相對應的消息。在native平臺上,MethodChannel在Android能夠接收方法調用並返回結果。這些類能夠幫助咱們用不多的代碼就能開發平臺插件。api
注意:本節內容來自flutter官網,讀者可自行查閱。數組
平臺通道可使用提供的編解碼器對消息進行編解碼,這些編解碼器支持簡單相似JSON的值的高效二進制序列化,例如布爾值,數字,字符串,字節緩衝區以及這些的列表和映射。當你發送和接收值時,會自動對這些值進行序列化和反序列化。網絡
下表顯示瞭如何在平臺端接收Dart值,反之亦然:架構
Dart | Android | iOS |
---|---|---|
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
int, if 64 bits not enough | java.math.BigInteger | FlutterStandardBigInteger |
double | java.lang.Double | NSNumber numberWithDouble: |
String | java.lang.String | NSString |
Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
List | java.util.ArrayList | NSArray |
Map | java.util.HashMap | NSDictionary |
關於編解碼器,Android端提供瞭如下四種。less
ByteBuffer
)。因爲BinaryCodec
在編解碼過程當中什麼都沒作,只是原封不動的將二進制數據返回。因此傳遞的數據在編解碼時會免於拷貝,這種方式在傳遞的數據量比較大時頗有用。好比從Android側傳入一張圖片到Flutter側顯示。BasicMessageChannel
的默認編解碼器,支持基礎數據類型、列表及字典等。在編碼時會先將數據寫入到ByteArrayOutputStream
流中,而後再將該流中的數據寫入到ByteBuffer
中。在解碼時,直接從ByteBuffer
中讀取數據。StandardMessageCodec
的封裝。是MethodChannel
與EventChannel
的默認編解碼器。ByteBuffer
中。在解碼時,直接從ByteBuffer
中讀取數據StringCodec
來實現編解碼。JSONMessageCodec
的封裝。能夠在MethodChannel
與EventChannel
中使用。
ByteBuffer
是Nio中的一個類,顧名思義——就是一塊存儲字節的區域。它有兩個實現類——DirectByteBuffer
與HeapByteBuffer
,DirectByteBuffer
是直接在內存中開闢了一塊區域來存儲數據,而HeapByteBuffer
是在JVM堆中開闢一塊區域來存儲數據,因此要想數據在DirectByteBuffer
中與HeapByteBuffer
互通,就須要進行一次拷貝。關於ByteBuffer
的更多內容能夠去閱讀Java NIO 的前生今世 之三 NIO Buffer 詳解這篇文章。異步
前面講了Android與flutter通訊的一些基礎知識,下面就進入正題,來看Android如何與flutter進行通訊。ide
Android與Flutter之間的通訊共有四種實現方式。
EventChannel
來實現,EventChannel
僅支持數據單向傳遞,無返回值。MethodChannel
來實現,MethodChannel
支持數據雙向傳遞,有返回值。BasicMessageChannel
來實現,BasicMessageChannel
支持數據雙向傳遞,有返回值。下面就來看一下這幾種方式的使用。
主要是利用了建立flutter頁面傳遞的route來作文章,筆者認爲該種方式屬於取巧,但仍是能夠用來傳遞數據。它的使用很簡單,代碼以下。
首先來看Android代碼。
//第三個參數能夠換成咱們想要字符串。
FlutterView flutterView = Flutter.createView(this, getLifecycle(), "route");
複製代碼
在flutter中,咱們只須要經過下面代碼來獲取值便可。
void main() => runApp(MyApp(
initParams: window.defaultRouteName,
));
class MyApp extends StatelessWidget {
final String initParams;//既是前面傳遞的值——route
MyApp({Key key, @required this.initParams}) : super(key: key);
@override
Widget build(BuildContext context) {...}
}
複製代碼
經過該種方式就能夠在初始化flutter時,Android給flutter傳遞數據。因爲runApp
僅會調用一次,因此該種方式只能傳遞一次數據且數據只能是字符串。
使用
window
的相關API須要導入包dart:ui
EventChannel
是一種native向flutter發送數據的單向通訊方式,flutter沒法返回任何數據給native。主要用於native向flutter發送手機電量變化、網絡鏈接變化、陀螺儀、傳感器等。它的使用方式以下。
首先來看Android代碼。
public class EventChannelPlugin implements EventChannel.StreamHandler {
private static final String TAG = EventChannelPlugin.class.getSimpleName();
private EventChannel.EventSink eventSink;
private Activity activity;
static EventChannelPlugin registerWith(FlutterView flutterView) {
EventChannelPlugin plugin = new EventChannelPlugin(flutterView);
new EventChannel(flutterView, "EventChannelPlugin").setStreamHandler(plugin);
return plugin;
}
private EventChannelPlugin(FlutterView flutterView) {
this.activity = (Activity) flutterView.getContext();
}
void send(Object params) {
if (eventSink != null) {
eventSink.success(params);
}
}
void sendError(String str1, String str2, Object params) {
if (eventSink != null) {
eventSink.error(str1, str2, params);
}
}
void cancel() {
if (eventSink != null) {
eventSink.endOfStream();
}
}
//第一個參數爲flutter初始化EventChannel時返回的值,僅此一次
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
this.eventSink = eventSink;
Log.i(TAG, "eventSink:" + eventSink);
Log.i(TAG, "Object:" + o.toString());
Toast.makeText(activity, "onListen——obj:" + o, Toast.LENGTH_SHORT).show();
}
@Override
public void onCancel(Object o) {
Log.i(TAG, "onCancel:" + o.toString());
Toast.makeText(activity, "onCancel——obj:" + o, Toast.LENGTH_SHORT).show();
this.eventSink = null;
}
}
複製代碼
筆者對Android端代碼作了一個簡單的封裝,仍是很好理解的。下面就來看flutter代碼實現。
class _MyHomePageState extends State<MyHomePage> {
EventChannel _eventChannelPlugin = EventChannel("EventChannelPlugin");
StreamSubscription _streamSubscription;
@override
void initState() {
_streamSubscription = _eventChannelPlugin
//["abc", 123, "你好"]對應着Android端onListen方法的第一個參數,可不傳值
.receiveBroadcastStream(["abc", 123, "你好"])
.listen(_onToDart, onError: _onToDartError, onDone: _onDone);
super.initState();
}
@override
void dispose() {
if (_streamSubscription != null) {
_streamSubscription.cancel();
_streamSubscription = null;
}
super.dispose();
}
//native端發送正常數據
void _onToDart(message) {
print(message);
}
//當native出錯時,發送的數據
void _onToDartError(error) {
print(error);
}
//當native發送數據完成時調用的方法,每一次發送完成就會調用
void _onDone() {
print("消息傳遞完畢");
}
@override
Widget build(BuildContext context) {...}
}
複製代碼
上面就是經過EventChannel
來進行通訊的代碼實現,調用EventChannelPlugin
的send
方法就能給flutter發送數據。
MethodChannel
是一種native與flutter之間互相發送數據的通訊方式,顧名思義,經過MethodChannel
就能調用native與flutter中相對應的方法,該種方式有返回值。它的使用方式以下。
首先來看Android端的代碼實現。
public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {
private Activity activity;
private MethodChannel channel;
public static MethodChannelPlugin registerWith(FlutterView flutterView) {
MethodChannel channel = new MethodChannel(flutterView, "MethodChannelPlugin");
MethodChannelPlugin methodChannelPlugin = new MethodChannelPlugin((Activity) flutterView.getContext(), channel);
channel.setMethodCallHandler(methodChannelPlugin);
return methodChannelPlugin;
}
private MethodChannelPlugin(Activity activity, MethodChannel channel) {
this.activity = activity;
this.channel = channel;
}
//調用flutter端方法,無返回值
public void invokeMethod(String method, Object o) {
channel.invokeMethod(method, o);
}
//調用flutter端方法,有返回值
public void invokeMethod(String method, Object o, MethodChannel.Result result) {
channel.invokeMethod(method, o, result);
}
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
switch (methodCall.method) {
case "send"://返回的方法名
//給flutter端的返回值
result.success("MethodChannelPlugin收到:" + methodCall.arguments);
Toast.makeText(activity, methodCall.arguments + "", Toast.LENGTH_SHORT).show();
if (activity instanceof FlutterAppActivity) {
((FlutterAppActivity) activity).showContent(methodCall.arguments);
}
break;
default:
result.notImplemented();
break;
}
}
}
複製代碼
筆者對Android端代碼作了一個簡單的封裝,仍是很好理解的。下面就來看flutter代碼實現。
class _MyHomePageState extends State<MyHomePage> {
MethodChannel _methodChannel = MethodChannel("MethodChannelPlugin");
@override
void initState() {
_methodChannel.setMethodCallHandler((handler) => Future<String>(() {
print("_methodChannel:${handler}");
//監聽native發送的方法名及參數
switch (handler.method) {
case "send":
_send(handler.arguments);//handler.arguments表示native傳遞的方法參數
break;
}
}));
super.initState();
}
//native調用的flutter方法
void _send(arg) {
setState(() {
_content = arg;
});
}
String _resultContent = "";
//flutter調用native的相應方法
void _sendToNative() {
Future<String> future =
_methodChannel.invokeMethod("send", _controller.text);
future.then((message) {
setState(() {
//message是native返回的數據
_resultContent = "返回值:" + message;
});
});
}
@override
Widget build(BuildContext context) {...}
}
複製代碼
上面就是經過MethodChannel
來進行通訊的代碼實現。仍是比較簡單的。在Android端使用只須要調用MethodChannelPlugin
的invokeMethod
方法便可。在flutter端使用只須要參考_sendToNative
方法的實現便可。
BasicMessageChannel
是一種可以在native與flutter之間互相發送消息的通訊方式,它支持數據類型最多,使用範圍最廣。EventChannel
與MethodChannel
的應用場景可使用BasicMessageChannel
來實現,但BasicMessageChannel
的應用場景就不必定可以使用EventChannel
與MethodChannel
來實現。該方式有返回值。它的使用方式以下。
首先來看Android代碼的實現。
//這裏支持的數據類型爲String。
public class BasicMessageChannelPlugin implements BasicMessageChannel.MessageHandler<String> {
private Activity activity;
private BasicMessageChannel<String> messageChannel;
static BasicMessageChannelPlugin registerWith(FlutterView flutterView) {
return new BasicMessageChannelPlugin(flutterView);
}
private BasicMessageChannelPlugin(FlutterView flutterView) {
this.activity = (Activity) flutterView.getContext();
this.messageChannel = new BasicMessageChannel<String>(flutterView, "BasicMessageChannelPlugin", StringCodec.INSTANCE);
messageChannel.setMessageHandler(this);
}
@Override
public void onMessage(String s, BasicMessageChannel.Reply<String> reply) {
reply.reply("BasicMessageChannelPlugin收到:" + s);
if (activity instanceof FlutterAppActivity) {
((FlutterAppActivity) activity).showContent(s);
}
}
void send(String str, BasicMessageChannel.Reply<String> reply) {
messageChannel.send(str, reply);
}
}
複製代碼
筆者對Android端代碼作了一個簡單的封裝,仍是很好理解的。下面就來看flutter代碼實現。
class _MyHomePageState extends State<MyHomePage> {
//StringCodec()爲編碼格式
BasicMessageChannel<String> _basicMessageChannel =
BasicMessageChannel("BasicMessageChannelPlugin", StringCodec());
@override
void initState() {
_basicMessageChannel.setMessageHandler((message) => Future<String>(() {
print(message);
//message爲native傳遞的數據
setState(() {
_content = message;
});
//給Android端的返回值
return "收到Native消息:" + message;
}));
_controller = TextEditingController();
super.initState();
}
//向native發送消息
void _sendToNative() {
Future<String> future = _basicMessageChannel.send(_controller.text);
future.then((message) {
_resultContent = "返回值:" + message;
});
}
@override
Widget build(BuildContext context) {...}
}
複製代碼
上面就是經過BasicMessageChannel
來進行通訊的代碼實現。在Android端只須要調用BasicMessageChannelPlugin
的send
方法就能夠向flutter發送數據,BasicMessageChannel.Reply<String>
是返回值的回調方法。在flutter端使用只須要參考_sendToNative
方法的實現便可。
從分析Android與Flutter通訊的源碼來看,實現仍是比較簡單的,都是以ByteBuffer
爲數據載體,而後經過BinaryMessenger
來發送與接收數據。總體設計以下。
從圖中能夠看出,Android側與flutter側採用了相同的設計。前面說過通訊時是異步進行的,那麼線程切換在哪?實際上是在系統底層實現的。在Android與Flutter通訊中,系統底層屏蔽了線程切換、數據拷貝等大量複雜操做。使得Android側與flutter側能方便的來進行通訊。
在Android側,BinaryMessenger
是一個接口,在FlutterView
中實現了該接口,在BinaryMessenger
的方法中經過JNI來與系統底層溝通。在Flutter側,BinaryMessenger
是一個類,該類的做用就是與類window
溝通,而類window
才真正與系統底層溝通。
關於通訊的底層實現能夠去閱讀閒魚的技術文章——深刻理解Flutter Platform Channel,這篇文章很好的講述了Flutter與Native通訊的系統底層原理。
在Android與Flutter混合開發模式下,相互之間通訊的場景確定不會少。瞭解Android與Flutter之間通訊的各類方式及使用,有助於選用合理的方式來實現。