Android與Flutter之間的通訊,8年Android程序員解析,一看就懂

一、架構概述

消息經過平臺通道在native(host)與flutter(client)之間傳遞,以下圖所示:數組

爲了確保用戶界面可以正確響應,消息都是以異步的方式進行傳遞。不管是native向flutter發送消息,仍是flutter向native發送消息。bash

在flutter中,MethodChannel能夠發送與方法調用相對應的消息。在native平臺上,MethodChannel在Android能夠接收方法調用並返回結果。這些類能夠幫助咱們用不多的代碼就能開發平臺插件。網絡

二、平臺通道數據類型支持和編解碼器

平臺通道可使用提供的編解碼器對消息進行編解碼,這些編解碼器支持簡單相似JSON的值的高效二進制序列化,例如布爾值,數字,字符串,字節緩衝區以及這些的列表和映射。當你發送和接收值時,會自動對這些值進行序列化和反序列化。架構

下表顯示瞭如何在平臺端接收Dart值,反之亦然:less

關於編解碼器,Android端提供瞭如下四種。異步

  • **BinaryCodec:**是最簡單的一種編解碼器,其返回值類型與入參的類型相同,均爲二進制格式(ByteBuffer)。因爲BinaryCodec在編解碼過程當中什麼都沒作,只是原封不動的將二進制數據返回。因此傳遞的數據在編解碼時會免於拷貝,這種方式在傳遞的數據量比較大時頗有用。好比從Android側傳入一張圖片到Flutter側顯示。
  • **StandardMessageCodec:**是BasicMessageChannel的默認編解碼器,支持基礎數據類型、列表及字典等。在編碼時會先將數據寫入到ByteArrayOutputStream流中,而後再將該流中的數據寫入到ByteBuffer中。在解碼時,直接從ByteBuffer中讀取數據。
  • **StandardMethodCodec:**是基於StandardMessageCodec的封裝。是MethodChannel與EventChannel的默認編解碼器。
  • **StringCodec:**是用於字符串與二進制數據之間的編解碼,其編碼格式爲UTF-8。在編碼時會將String轉成byte數組,而後再將該數組寫入到ByteBuffer中。在解碼時,直接從ByteBuffer中讀取數據
  • **JSONMessageCodec:**內部調用StringCodec來實現編解碼。
  • **JSONMethodCodec:**基於JSONMessageCodec的封裝。能夠在MethodChannel與EventChannel中使用。

ByteBuffer是Nio中的一個類,顧名思義——就是一塊存儲字節的區域。它有兩個實現類——DirectByteBuffer與HeapByteBuffer,DirectByteBuffer是直接在內存中開闢了一塊區域來存儲數據,而HeapByteBuffer是在JVM堆中開闢一塊區域來存儲數據,因此要想數據在DirectByteBuffer中與HeapByteBuffer互通,就須要進行一次拷貝。ide

三、通訊方式

前面講了Android與flutter通訊的一些基礎知識,下面就進入正題,來看Android如何與flutter進行通訊。ui

Android與Flutter之間的通訊共有四種實現方式。this

  1. 因爲在初始化flutter頁面時會傳遞一個字符串——route,所以咱們就能夠拿route來作文章,傳遞本身想要傳遞的數據。該種方式僅支持單向數據傳遞且數據類型只能爲字符串,無返回值。
  2. 經過EventChannel來實現,EventChannel僅支持數據單向傳遞,無返回值。
  3. 經過MethodChannel來實現,MethodChannel支持數據雙向傳遞,有返回值。
  4. 經過BasicMessageChannel來實現,BasicMessageChannel支持數據雙向傳遞,有返回值。

下面就來看一下這幾種方式的使用。編碼

3.一、初始化時傳值

主要是利用了建立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

3.二、EventChannel

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發送數據。

3.三、MethodChannel

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方法的實現便可。

3.四、BasicMessageChannel

BasicMessageChannel是一種可以在native與flutter之間互相發送消息的通訊方式,它支持數據類型最多,使用範圍最廣。EventChannel與MethodChannel的應用場景可使用BasicMessageChannel來實現,但BasicMessageChannel的應用場景就不必定可以使用EventChannel與MethodChannel來實現。該方式有返回值。它的使用方式以下。

首先來看Android代碼的實現。

//這裏支持的數據類型爲String。

//這裏支持的數據類型爲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是返回值的回調方法。在flutter端使用只須要參考_sendToNative方法的實現便可。

四、通訊原理

從分析Android與Flutter通訊的源碼來看,實現仍是比較簡單的,都是以ByteBuffer爲數據載體,而後經過BinaryMessenger來發送與接收數據。總體設計以下。

從圖中能夠看出,Android側與flutter側採用了相同的設計。前面說過通訊時是異步進行的,那麼線程切換在哪?實際上是在系統底層實現的。在Android與Flutter通訊中,系統底層屏蔽了線程切換、數據拷貝等大量複雜操做。使得Android側與flutter側能方便的來進行通訊。

在Android側,BinaryMessenger是一個接口,在FlutterView中實現了該接口,在BinaryMessenger的方法中經過JNI來與系統底層溝通。在Flutter側,BinaryMessenger是一個類,該類的做用就是與類window溝通,而類window才真正與系統底層溝通。

五、總結

在Android與Flutter混合開發模式下,相互之間通訊的場景確定不會少。瞭解Android與Flutter之間通訊的各類方式及使用,有助於選用合理的方式來實現。

最後

若是你看到了這裏,以爲文章寫得不錯就給個讚唄?若是你以爲那裏值得改進的,請給我留言。必定會認真查詢,修正不足。謝謝。

爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個小贊關注下我,之後還會更新技術乾貨,謝謝您的支持!

轉發分享+關注,天天獲取更多資料

Android架構師之路很漫長,一塊兒共勉吧!

相關文章
相關標籤/搜索