一篇看懂Android與Flutter之間的通信
Flutter作為一種跨平臺解決方案,經(jīng)常會作為一個模塊嵌入到原生Android與iOS應用中,F(xiàn)lutter與Android原生端的通信必不可少。所以本文就來講述一下Android如何與flutter進行通信。
1、架構(gòu)概述
消息通過平臺通道在native(host)與flutter(client)之間傳遞,如下圖所示:
為了確保用戶界面能夠正確響應,消息都是以異步的方式進行傳遞。無論是native向flutter發(fā)送消息,還是flutter向native發(fā)送消息。
在flutter中,MethodChannel可以發(fā)送與方法調(diào)用相對應的消息。在native平臺上,MethodChannel在Android可以接收方法調(diào)用并返回結(jié)果。這些類可以幫助我們用很少的代碼就能開發(fā)平臺插件。
注意:本節(jié)內(nèi)容來自flutter官網(wǎng),讀者可自行查閱。
2、平臺通道數(shù)據(jù)類型支持和編解碼器
平臺通道可以使用提供的編解碼器對消息進行編解碼,這些編解碼器支持簡單類似JSON的值的高效二進制序列化,例如布爾值,數(shù)字,字符串,字節(jié)緩沖區(qū)以及這些的列表和映射。當你發(fā)送和接收值時,會自動對這些值進行序列化和反序列化。
下表顯示了如何在平臺端接收Dart值,反之亦然:
關(guān)于編解碼器,Android端提供了以下幾種。
- BinaryCodec:是最簡單的一種編解碼器,其返回值類型與入?yún)⒌念愋拖嗤?,均為二進制格式(ByteBuffer)。由于BinaryCodec在編解碼過程中什么都沒做,只是原封不動的將二進制數(shù)據(jù)返回。所以傳遞的數(shù)據(jù)在編解碼時會免于拷貝,這種方式在傳遞的數(shù)據(jù)量比較大時很有用。比如從Android側(cè)傳入一張圖片到Flutter側(cè)顯示。
- StandardMessageCodec:是BasicMessageChannel的默認編解碼器,支持基礎(chǔ)數(shù)據(jù)類型、列表及字典等。在編碼時會先將數(shù)據(jù)寫入到ByteArrayOutputStream流中,然后再將該流中的數(shù)據(jù)寫入到ByteBuffer中。在解碼時,直接從ByteBuffer中讀取數(shù)據(jù)。
- StandardMethodCodec:是基于StandardMessageCodec的封裝。是MethodChannel與EventChannel的默認編解碼器。
- StringCodec:是用于字符串與二進制數(shù)據(jù)之間的編解碼,其編碼格式為UTF-8。在編碼時會將String轉(zhuǎn)成byte數(shù)組,然后再將該數(shù)組寫入到ByteBuffer中。在解碼時,直接從ByteBuffer中讀取數(shù)據(jù)
- JSONMessageCodec:內(nèi)部調(diào)用StringCodec來實現(xiàn)編解碼。
- JSONMethodCodec:基于JSONMessageCodec的封裝??梢栽贛ethodChannel與EventChannel中使用。
ByteBuffer是Nio中的一個類,顧名思義——就是一塊存儲字節(jié)的區(qū)域。它有兩個實現(xiàn)類——DirectByteBuffer與HeapByteBuffer,DirectByteBuffer是直接在內(nèi)存中開辟了一塊區(qū)域來存儲數(shù)據(jù),而HeapByteBuffer是在JVM堆中開辟一塊區(qū)域來存儲數(shù)據(jù),所以要想數(shù)據(jù)在DirectByteBuffer中與HeapByteBuffer互通,就需要進行一次拷貝。
3、通信方式
前面講了Android與flutter通信的一些基礎(chǔ)知識,下面就進入正題,來看Android如何與flutter進行通信。
Android與Flutter之間的通信共有四種實現(xiàn)方式。
- 由于在初始化flutter頁面時會傳遞一個字符串——route,因此我們就可以拿route來做文章,傳遞自己想要傳遞的數(shù)據(jù)。該種方式僅支持單向數(shù)據(jù)傳遞且數(shù)據(jù)類型只能為字符串,無返回值。
- 通過EventChannel來實現(xiàn),EventChannel僅支持數(shù)據(jù)單向傳遞,無返回值。
- 通過MethodChannel來實現(xiàn),MethodChannel支持數(shù)據(jù)雙向傳遞,有返回值。
- 通過BasicMessageChannel來實現(xiàn),BasicMessageChannel支持數(shù)據(jù)雙向傳遞,有返回值。
下面就來看一下這幾種方式的使用。
3.1、初始化時傳值
主要是利用了創(chuàng)建flutter頁面?zhèn)鬟f的route來做文章,筆者認為該種方式屬于取巧,但還是可以用來傳遞數(shù)據(jù)。它的使用很簡單,代碼如下。
首先來看Android代碼。
- //第三個參數(shù)可以換成我們想要字符串。
- FlutterView flutterView = Flutter.createView(this, getLifecycle(), "route");
在flutter中,我們只需要通過下面代碼來獲取值即可。
- void main() => runApp(MyApp(
- initParams: window.defaultRouteName,
- ));
- class MyApp extends StatelessWidget {
- final String initParams;//既是前面?zhèn)鬟f的值——route
- MyApp({Key key, @required this.initParams}) : super(key: key);
- @override
- Widget build(BuildContext context) {...}
- }
通過該種方式就可以在初始化flutter時,Android給flutter傳遞數(shù)據(jù)。由于runApp僅會調(diào)用一次,所以該種方式只能傳遞一次數(shù)據(jù)且數(shù)據(jù)只能是字符串。
- 使用window的相關(guān)API需要導入包dart:ui
3.2、EventChannel
EventChannel是一種native向flutter發(fā)送數(shù)據(jù)的單向通信方式,flutter無法返回任何數(shù)據(jù)給native。主要用于native向flutter發(fā)送手機電量變化、網(wǎng)絡(luò)連接變化、陀螺儀、傳感器等。它的使用方式如下。
首先來看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();
- }
- }
- //第一個參數(shù)為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代碼實現(xiàn)。
- class _MyHomePageState extends State<MyHomePage> {
- EventChannel _eventChannelPlugin = EventChannel("EventChannelPlugin");
- StreamSubscription _streamSubscription;
- @override
- void initState() {
- _streamSubscription = _eventChannelPlugin
- //["abc", 123, "你好"]對應著Android端onListen方法的第一個參數(shù),可不傳值
- .receiveBroadcastStream(["abc", 123, "你好"])
- .listen(_onToDart, onError: _onToDartError, onDone: _onDone);
- super.initState();
- }
- @override
- void dispose() {
- if (_streamSubscription != null) {
- _streamSubscription.cancel();
- _streamSubscription = null;
- }
- super.dispose();
- }
- //native端發(fā)送正常數(shù)據(jù)
- void _onToDart(message) {
- print(message);
- }
- //當native出錯時,發(fā)送的數(shù)據(jù)
- void _onToDartError(error) {
- print(error);
- }
- //當native發(fā)送數(shù)據(jù)完成時調(diào)用的方法,每一次發(fā)送完成就會調(diào)用
- void _onDone() {
- print("消息傳遞完畢");
- }
- @override
- Widget build(BuildContext context) {...}
- }
上面就是通過EventChannel來進行通信的代碼實現(xiàn),調(diào)用EventChannelPlugin的send方法就能給flutter發(fā)送數(shù)據(jù)。
3.3、MethodChannel
MethodChannel是一種native與flutter之間互相發(fā)送數(shù)據(jù)的通信方式,顧名思義,通過MethodChannel就能調(diào)用native與flutter中相對應的方法,該種方式有返回值。它的使用方式如下。
首先來看Android端的代碼實現(xiàn)。
- 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;
- }
- //調(diào)用flutter端方法,無返回值
- public void invokeMethod(String method, Object o) {
- channel.invokeMethod(method, o);
- }
- //調(dià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代碼實現(xiàn)。
- class _MyHomePageState extends State<MyHomePage> {
- MethodChannel _methodChannel = MethodChannel("MethodChannelPlugin");
- @override
- void initState() {
- _methodChannel.setMethodCallHandler((handler) => Future<String>(() {
- print("_methodChannel:${handler}");
- //監(jiān)聽native發(fā)送的方法名及參數(shù)
- switch (handler.method) {
- case "send":
- _send(handler.arguments);//handler.arguments表示native傳遞的方法參數(shù)
- break;
- }
- }));
- super.initState();
- }
- //native調(diào)用的flutter方法
- void _send(arg) {
- setState(() {
- _content = arg;
- });
- }
- String _resultContent = "";
- //flutter調(diào)用native的相應方法
- void _sendToNative() {
- Future<String> future =
- _methodChannel.invokeMethod("send", _controller.text);
- future.then((message) {
- setState(() {
- //message是native返回的數(shù)據(jù)
- _resultContent = "返回值:" + message;
- });
- });
- }
- @override
- Widget build(BuildContext context) {...}
- }
上面就是通過MethodChannel來進行通信的代碼實現(xiàn)。還是比較簡單的。在Android端使用只需要調(diào)用MethodChannelPlugin的invokeMethod方法即可。在flutter端使用只需要參考_sendToNative方法的實現(xiàn)即可。
3.4、BasicMessageChannel
BasicMessageChannel是一種能夠在native與flutter之間互相發(fā)送消息的通信方式,它支持數(shù)據(jù)類型最多,使用范圍最廣。EventChannel與MethodChannel的應用場景可以使用BasicMessageChannel來實現(xiàn),但BasicMessageChannel的應用場景就不一定能夠使用EventChannel與MethodChannel來實現(xiàn)。該方式有返回值。它的使用方式如下。
首先來看Android代碼的實現(xiàn)。
- //這里支持的數(shù)據(jù)類型為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代碼實現(xiàn)。
- class _MyHomePageState extends State<MyHomePage> {
- //StringCodec()為編碼格式
- BasicMessageChannel<String> _basicMessageChannel =
- BasicMessageChannel("BasicMessageChannelPlugin", StringCodec());
- @override
- void initState() {
- _basicMessageChannel.setMessageHandler((message) => Future<String>(() {
- print(message);
- //message為native傳遞的數(shù)據(jù)
- setState(() {
- _content = message;
- });
- //給Android端的返回值
- return "收到Native消息:" + message;
- }));
- _controller = TextEditingController();
- super.initState();
- }
- //向native發(fā)送消息
- void _sendToNative() {
- Future<String> future = _basicMessageChannel.send(_controller.text);
- future.then((message) {
- _resultContent = "返回值:" + message;
- });
- }
- @override
- Widget build(BuildContext context) {...}
- }
上面就是通過BasicMessageChannel來進行通信的代碼實現(xiàn)。在Android端只需要調(diào)用BasicMessageChannelPlugin的send方法就可以向flutter發(fā)送數(shù)據(jù),BasicMessageChannel.Reply是返回值的回調(diào)方法。在flutter端使用只需要參考_sendToNative方法的實現(xiàn)即可。
4、通信原理
從分析Android與Flutter通信的源碼來看,實現(xiàn)還是比較簡單的,都是以ByteBuffer為數(shù)據(jù)載體,然后通過BinaryMessenger來發(fā)送與接收數(shù)據(jù)。整體設(shè)計如下。
從圖中可以看出,Android側(cè)與flutter側(cè)采用了相同的設(shè)計。前面說過通信時是異步進行的,那么線程切換在哪?其實是在系統(tǒng)底層實現(xiàn)的。在Android與Flutter通信中,系統(tǒng)底層屏蔽了線程切換、數(shù)據(jù)拷貝等大量復雜操作。使得Android側(cè)與flutter側(cè)能方便的來進行通信。
在Android側(cè),BinaryMessenger是一個接口,在FlutterView中實現(xiàn)了該接口,在BinaryMessenger的方法中通過JNI來與系統(tǒng)底層溝通。在Flutter側(cè),BinaryMessenger是一個類,該類的作用就是與類window溝通,而類window才真正與系統(tǒng)底層溝通。
5、總結(jié)
在Android與Flutter混合開發(fā)模式下,相互之間通信的場景肯定不會少。了解Android與Flutter之間通信的各種方式及使用,有助于選用合理的方式來實現(xiàn)。
最后
如果你看到了這里,覺得文章寫得不錯就給個贊唄?如果你覺得那里值得改進的,請給我留言。一定會認真查詢,修正不足。謝謝。