一篇看懂Android與Flutter之間的通信

2020-09-03 以碼為夢

Flutter作為一種跨平臺解決方案,經常會作為一個模塊嵌入到原生Android與iOS應用中,Flutter與Android原生端的通信必不可少。所以本文就來講述一下Android如何與flutter進行通信。

1、架構概述

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


為了確保用戶界面能夠正確響應,消息都是以異步的方式進行傳遞。無論是native向flutter發送消息,還是flutter向native發送消息。

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

注意:本節內容來自flutter官網,讀者可自行查閱。


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

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

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

關於編解碼器,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互通,就需要進行一次拷貝。


3、通信方式

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

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

  1. 由於在初始化flutter頁面時會傳遞一個字符串——route,因此我們就可以拿route來做文章,傳遞自己想要傳遞的數據。該種方式僅支持單向數據傳遞且數據類型只能為字符串,無返回值。
  2. 通過EventChannel來實現,EventChannel僅支持數據單向傳遞,無返回值。
  3. 通過MethodChannel來實現,MethodChannel支持數據雙向傳遞,有返回值。
  4. 通過BasicMessageChannel來實現,BasicMessageChannel支持數據雙向傳遞,有返回值。

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

3.1、初始化時傳值

主要是利用了創建flutter頁面傳遞的route來做文章,筆者認為該種方式屬於取巧,但還是可以用來傳遞數據。它的使用很簡單,代碼如下。

首先來看Android代碼。

//第三個參數可以換成我們想要字符串。FlutterView flutterView = Flutter.createView(this, getLifecycle(), &34;);

在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.2、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, &34;).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, &34; + eventSink); Log.i(TAG, &34; + o.toString()); Toast.makeText(activity, &34; + o, Toast.LENGTH_SHORT).show(); } @Override public void onCancel(Object o) { Log.i(TAG, &34; + o.toString()); Toast.makeText(activity, &34; + o, Toast.LENGTH_SHORT).show(); this.eventSink = null; }}

筆者對Android端代碼做了一個簡單的封裝,還是很好理解的。下面就來看flutter代碼實現。

class _MyHomePageState extends State<MyHomePage> { EventChannel _eventChannelPlugin = EventChannel(&34;); StreamSubscription _streamSubscription; @override void initState() { _streamSubscription = _eventChannelPlugin //[&34;, 123, &34;]對應著Android端onListen方法的第一個參數,可不傳值 .receiveBroadcastStream([&34;, 123, &34;]) .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(&34;); } @override Widget build(BuildContext context) {...}}

上面就是通過EventChannel來進行通信的代碼實現,調用EventChannelPlugin的send方法就能給flutter發送數據。

3.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, &34;); 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 &34;://返回的方法名 //給flutter端的返回值 result.success(&34; + methodCall.arguments); Toast.makeText(activity, methodCall.arguments + &34;, 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(&34;); @override void initState() { _methodChannel.setMethodCallHandler((handler) => Future<String>(() { print(&34;); //監聽native發送的方法名及參數 switch (handler.method) { case &34;: _send(handler.arguments);//handler.arguments表示native傳遞的方法參數 break; } })); super.initState(); } //native調用的flutter方法 void _send(arg) { setState(() { _content = arg; }); } String _resultContent = &34;; //flutter調用native的相應方法 void _sendToNative() { Future<String> future = _methodChannel.invokeMethod(&34;, _controller.text); future.then((message) { setState(() { //message是native返回的數據 _resultContent = &34; + message; }); }); } @override Widget build(BuildContext context) {...}}

上面就是通過MethodChannel來進行通信的代碼實現。還是比較簡單的。在Android端使用只需要調用MethodChannelPlugin的invokeMethod方法即可。在flutter端使用只需要參考_sendToNative方法的實現即可。

3.4、BasicMessageChannel

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, &34;, StringCodec.INSTANCE); messageChannel.setMessageHandler(this); } @Override public void onMessage(String s, BasicMessageChannel.Reply<String> reply) { reply.reply(&34; + 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(&34;, StringCodec()); @override void initState() { _basicMessageChannel.setMessageHandler((message) => Future<String>(() { print(message); //message為native傳遞的數據 setState(() { _content = message; }); //給Android端的返回值 return &34; + message; })); _controller = TextEditingController(); super.initState(); } //向native發送消息 void _sendToNative() { Future<String> future = _basicMessageChannel.send(_controller.text); future.then((message) { _resultContent = &34; + message; }); } @override Widget build(BuildContext context) {...}}

上面就是通過BasicMessageChannel來進行通信的代碼實現。在Android端只需要調用BasicMessageChannelPlugin的send方法就可以向flutter發送數據,BasicMessageChannel.Reply<String>是返回值的回調方法。在flutter端使用只需要參考_sendToNative方法的實現即可。


4、通信原理

從分析Android與Flutter通信的源碼來看,實現還是比較簡單的,都是以ByteBuffer為數據載體,然後通過BinaryMessenger來發送與接收數據。整體設計如下。

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

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

關於通信的底層實現可以去閱讀閒魚的技術文章——深入理解Flutter Platform Channel,這篇文章很好的講述了Flutter與Native通信的系統底層原理。


5、總結

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


6、最後

當然了,本文所列出的知識點還不完全,還是要比較系統的學習,我這裡剛好有一份Flutter的學習資料

還有一份大佬收錄整理的Android學習PDF+架構視頻+面試文檔+源碼筆記高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料

這些都是我現在閒暇還會反覆翻閱的精品資料。裡面對近幾年的大廠面試高頻知識點都有詳細的講解。相信可以有效的幫助大家掌握知識、理解原理。

當然你也可以拿去查漏補缺,提升自身的競爭力。

如果你有需要,可以私信或評論我獲取

喜歡本文的話,不妨順手給我點個讚、評論區留言或者轉發支持一下唄~


相關焦點

  • Flutter中嵌套Android布局
    創建Android中的布局: <io.flutter.embedding.android.FlutterView android:id="@+id/flutter_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight
  • 實操|在 Flutter 中創建通信橋
    我在之前的一篇文章中解釋了如何在 Android 和 iOS 中創建通信橋,作為後續,我認為解釋一下如何在 Flutter 中創建通信橋也是一個不錯的想法。雖然這可能看起來是一件很簡單的事情,但你很快就會意識到,要使這個功能正常工作需要一些工作。首先,重要的是意識到(在撰寫本文時)Flutter 還沒有內置對嵌入式 WebView 的支持。
  • 最新版flutter和android原生交互,必須掌握
    我之前也寫過一篇文章,講解了flutter 與android的原生交互,那個flutter版本是1.9.x,最新穩定版是1.17.5。我們就拿這個版本來講解下flutter與android原生交互。/plugins/GeneratedPluginRegistrant,如果有這個類那就用我下面的方法來實現與android原生的交互。
  • Flutter入門系列-Android Studio首次運行flutter卡住
    之前我們學過了flutter項目安裝與環境搭建我運行了項目,發現一直卡在這裡Running Gradle task &39;...通過在網上查詢,有可能是Gradle問題,需要配置鏡像地址修改項目中android/build.gradle文件allprojects { repositories { maven
  • Flutter 二維碼掃描插件
    'dart:async';import 'package:flutter/services.dart';import 'package:flutter_plugin_qrcode/flutter_plugin_qrcode.dart';void main() => runApp(MyApp());class MyApp extends StatefulWidget {
  • Flutter與Android iOS 的雙向通信
    Flutter 與 Android iOS 原生的通信有以下三種方式 BasicMessageChannel 實現 Flutter 與 原生(Android 、iOS)雙向通信MethodChannel 實現 Flutter 與 原生原生(Android 、iOS)雙向通信
  • Flutter 混合開發與原生通信MethodChannel
    作者:老孟Flutter平臺通信的3種方式Flutter 與 Native 端通信有如下3個方法:MethodChannel:Flutter 與 Native 端相互調用,調用後可以返回結果,可以 Native
  • 跨平臺Flutter接入WebView,Android和IOS兩大平臺解決方案建收藏
    今天就為大家講講Flutter中WebView的詳細使用方法,手機開始之前先簡單了解一下官方WebView所包含的API:onWebViewCreated:在WebView創建完成後調用,只會被調用一次;initialUrl:初始load的url;javascriptMode:JS執行模式(是否允許JS執行);javascriptChannels:JS和Flutter通信的
  • 「Flutter 1-2」在 Windows 10下安裝Flutter+Dart+Android Studio...
    解壓成功之後,我們需要將Flutter也配置到環境變量中,flutter文件夾下的bin目錄路徑(我這裡是C:\src\flutter\bin)配置到環境變量中。>運行 flutter doctor 將路徑配置之後我們打開 終端工具PowerShell 輸入 flutter doctor查看還有哪些需要配置。
  • ...1-2」在 Windows 10下安裝Flutter+Dart+Android Studio 配置...
    下載完成後在新建一個文件夾解壓縮,我這裡選的是C:\src\flutter 解壓成功之後,我們需要將Flutter也配置到環境變量中,flutter文件夾下的bin目錄路徑(我這裡是C:\src\flutter\bin)配置到環境變量中。
  • 移動端跨平臺UI框架flutter開發環境搭建指南
    下一篇文章中會介紹Dart開發環境的搭建和Dart語言編譯成機器碼運行。以mac os為例,首先要安裝好下面的工具:bash、curl、git、mkdir、rm、unzip、which、zip在安裝flutter中會用到這些命令。由於在國內google的flutter依賴庫可能用不了,所以google提供了官方鏡像地址。
  • flutter 編譯生成apk失敗
    It's likely that this file was generated under通過二分法查找,執行了多次編譯比較後發現,是由於android目錄build.gradle使用的gradle編譯版本和flutter/packages/flutter_tools/gradle/flutter.gradle中gradle版本不一致導致的。
  • Flutter 加載本地圖像教程 Android iOS 通用 建議收藏
    本頭條核心宗旨歡迎來到「技術剛剛好」作者,「技術剛剛好」是個人維護,每天至少更新一篇Flutter技術文章,實時為大家播報Flutter最新消息。如果你剛好也在關注Flutter這門技術,那就跟我一起學習進步吧,你的贊,收藏,轉發是對我個人最大的支持,維護不易,歡迎關注。
  • 從新手到Flutter架構師,一篇就夠!最全開源項目
    : flutter_test: sdk: flutterflutter: uses-material-design: true其中,我們依賴的第三方庫就放在dependencies節點下面。該插件支持ios和android平臺,使用的是ios的風格的UI效果。)
  • Flutter插件用於在移動平臺中播放視頻支持iOS和Android建議收藏
    這篇文章介紹一下在Flutter當中播放視頻,我們用到了第三方到一個插件,目前flutter上面還沒有原生支持,可以在pub.dev上面找一些插件來解決。本百家號核心宗旨歡迎來到「技術剛剛好」百家號,本百家號是個人維護,每天至少更新一篇Flutter技術文章,實時為大家播報Flutter最新消息。
  • Flutter加載網頁之官方webview_flutter講解
    前言最近在用flutter開發一個App,其中需要加載網頁,本來以為很簡單的一件事,卻遇到了不少坑,最後整整搞了一上午。對於加載網頁這個功能來說,flutter並沒有組件,反而是第三方開發了一些組件,目前比較常用的有二種: flutter_webview_plugin webview_flutterflutter_webview_plugin是目前用得最火的一種,也是比較好用的,但是它有個致命的缺點,它不支持「進入某個URL之前攔截」,這也是我放棄它的原因。
  • Flutter源碼剖析(一):源碼獲取與構建
    /flutter/tools/gn --unoptimized --android --runtime-mode debug --android-cpu armGenerating GN files in: out/android_debug_unoptGenerating Xcode projects took 75msDone.
  • Flutter應用開發之第一步(踩坑記)
    首先,flutter doctor會認為android有問題,這個可以通過修改環境變量來解決,之前用的環境變量名是ANDROID_HOME,現在是ANDROID_SDK_ROOT。其次是cocopods問題,雖然檢測出問題,但是實際上毫無影響。然後就是plugin問題,檢測不到flutter插件和dart插件,但實際上是安裝了。
  • 創建並運行自己的第一個 Flutter項目
    最近在學習flutter,畢竟是第一次運行flutter項目,中間遇到很多問題,今天就把自己如何創建並運行flutter項目,及創建的過程中遇到的一些問題都總結出來,方便後來人查看。先來講講flutter的下載與安裝。
  • Flutter 下載和配置
    Flutter工具配置1、 Git我們需要下載Git直接去Git官網下載並配置地址:https://git-scm.com2、Android開發工具下載地址:https://developer.android.google.cn