Flutter 混合開發(Android)Flutter跟Native相互通信

前言

Flutter 作為混合開發,跟native端做一些交互在所難免,比如說調用原生系統傳感器、原生端的網絡框架進行數據請求就會用到 Flutter 調用android 及android 原生調用 Flutter的方法,這里就涉及到Platform Channels(平臺通道)

Platform Channels (平臺通道)

Flutter 通過Channel 與客戶端之間傳遞消息,如圖:

image.png

圖中就是通過MethodChannel的方式實現Flutter 與客戶端之間的消息傳遞。MethodChannel是Platform Channels中的一種,Flutter有三種通信類型:

BasicMessageChannel:用于傳遞字符串和半結構化的信息

MethodChannel:用于傳遞方法調用(method invocation)通常用來調用native中某個方法

EventChannel: 用于數據流(event streams)的通信。有監聽功能,比如電量變化之后直接推送數據給flutter端。

為了保證UI的響應,通過Platform Channels傳遞的消息都是異步的。

更多關于channel原理可以去看這篇文章:channel原理篇

Platform Channels 使用

1.MethodChannel的使用

原生客戶端寫法(以Android 為例)

首先定義一個獲取手機電量方法

private int getBatteryLevel() {
        return 90;
    }

這函數是要給Flutter 調用的方法,此時就需要通過 MethodChannel 來建立這個通道了。

首先新增一個初始化 MethodChannel 的方法

private String METHOD_CHANNEL = "common.flutter/battery";
private String GET_BATTERY_LEVEL = "getBatteryLevel";
private MethodChannel methodChannel;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        initMethodChannel();
        getFlutterView().postDelayed(() ->
            methodChannel.invokeMethod("get_message", null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object o) {
                    Log.d(TAG, "get_message:" + o.toString());
                }

                @Override
                public void error(String s, @Nullable String s1, @Nullable Object o) {

                }

                @Override
                public void notImplemented() {

                }
            }), 5000);

    }

    private void initMethodChannel() {
        methodChannel = new MethodChannel(getFlutterView(), METHOD_CHANNEL);
        methodChannel.setMethodCallHandler(
                (methodCall, result) -> {
                    if (methodCall.method.equals(GET_BATTERY_LEVEL)) {
                        int batteryLevel = getBatteryLevel();

                        if (batteryLevel != -1) {
                            result.success(batteryLevel);
                        } else {
                            result.error("UNAVAILABLE", "Battery level not available.", null);
                        }
                    } else {
                        result.notImplemented();
                    }
                });


    }

    private int getBatteryLevel() {
        return 90;
    }

METHOD_CHANNEL 用于和flutter交互的標識,由于一般情況下會有多個channel,在app里面需要保持唯一性

MethodChannel 都是保存在以通道名為Key的Map中。所以要是設了兩個名字一樣的channel,只有后設置的那個會生效。

onMethodCall 有兩個參數,onMethodCall 里包含要調用的方法名稱和參數。Result是給Flutter的返回值。方法名是客戶端與Flutter統一設定。通過if/switch語句判斷 MethodCall.method 來區分不同的方法,在我們的例子里面我們只會處理名為“getBatteryLevel”的調用。在調用本地方法獲取到電量以后通過 result.success(batteryLevel) 調用把電量值返回給Flutter。

MethodChannel-Flutter 端

直接先看一下Flutter端的代碼

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  static const platform = const MethodChannel('common.flutter/battery');

  void _incrementCounter() {
    setState(() {
      _counter++;
      _getBatteryLevel();
    });
  }

  @override
  Widget build(BuildContext context) {
    platform.setMethodCallHandler(platformCallHandler);
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            Text('$_batteryLevel'),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }

  String _batteryLevel = 'Unknown battery level.';

  Future<Null> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

 //客戶端調用
  Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "get_message":
        return "Hello from Flutter";
        break;
    }
  }
}

上面代碼解析:
首先,定義一個常量result.success(platform),和Android客戶端定義的channel一致;
接下來定義一個 result.success(_getBatteryLevel())方法,用來調用Android 端的方法,result.success(final int result = await platform.invokeMethod('getBatteryLevel');) 這行代碼就是通過通道來調用Native(Android)方法了。因為MethodChannel是異步調用的,所以這里必須要使用await關鍵字。

在上面Android代碼中我們把獲取到的電量通過result.success(batteryLevel);返回給Flutter。這里await表達式執行完成以后電量就直接賦值給result變量了。然后通過result.success(setState); 去改變Text顯示值。到這里為止,是通過Flutter端調用原生客戶端方法。

MethodChannel 其實是一個可以雙向調用的方法,在上面的代碼中,其實我們也體現了,通過原生客戶端調用Flutter的方法。

在原生端通過 methodChannel.invokeMethod 的方法調用

methodChannel.invokeMethod("get_message", null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object o) {
                    Log.d(TAG, "get_message:" + o.toString());
                }

                @Override
                public void error(String s, @Nullable String s1, @Nullable Object o) {

                }

                @Override
                public void notImplemented() {

                }
            });

在Flutter端就需要給MethodChannel設置一個MethodCallHandler

static const platform = const MethodChannel('common.flutter/battery');
platform.setMethodCallHandler(platformCallHandler);
Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "get_message":
        return "Hello from Flutter";
        break;
    }
  }

以上就是MethodChannel的相關用法了。

EventChannel

將數據推送給Flutter端,類似我們常用的推送功能,有需要就推送給Flutter端,是否需要去處理這個推送由Flutter那邊決定。相對于MethodChannel是主動獲取,EventChannel則是被動推送。

EventChannel 原生客戶端寫法

private String EVENT_CHANNEL = "common.flutter/message";
private int count = 0;
private Timer timer;

private void initEventChannel() {
        new EventChannel(getFlutterView(), EVENT_CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object arguments, EventChannel.EventSink events) {
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        if (count < 10) {
                            count++;
                            events.success("當前時間:" + System.currentTimeMillis());
                        } else {
                            timer.cancel();
                        }
                    }
                }, 1000, 1000);
            }

            @Override
            public void onCancel(Object o) {

            }
        });
    }

在上面的代碼中,我們做了一個定時器,每秒向Flutter推送一個消息,告訴Flutter我們當前時間。為了防止一直倒計時,我這邊做了個計數,超過10次就停止發送。

EventChannel Flutter端

String message = "not message";
static const eventChannel = const EventChannel('common.flutter/message');
@override
  void initState() {
    super.initState();
    eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  }

void _onEvent(Object event) {
    setState(() {
      message =
      "message: $event";
    });
  }

  void _onError(Object error) {
    setState(() {
      message = 'message: unknown.';
    });
  }

上面的代碼就是Flutter端接收原生客戶端數據,通過_onEvent 來接收數據,將數據顯示Text。這個實現相對簡單,如果要達到業務分類,需要將數據封裝成json,通過json數據包裝一些對應業務標識和數據來做區分。

BasicMessageChannel

BasicMessageChannel (主要是傳遞字符串和一些半結構體的數據)

BasicMessageChannel Android端

private void initBasicMessageChannel() {
        BasicMessageChannel<Object> basicMessageChannel = new BasicMessageChannel<>(getFlutterView(), BASIC_CHANNEL, StandardMessageCodec.INSTANCE);
        //主動發送消息到flutter 并接收flutter消息回復
            basicMessageChannel.send("send basic message", (object)-> {
                Log.e(TAG, "receive reply msg from flutter:" + object.toString());
            });

        //接收flutter消息 并發送回復
        basicMessageChannel.setMessageHandler((object, reply)-> {
            Log.e(TAG, "receive msg from flutter:" + object.toString());
            reply.reply("reply:got your message");

        });

    }

BasicMessageChannel Flutter端

  static const basicChannel = const BasicMessageChannel('common.flutter/basic', StandardMessageCodec());
//發送消息到原生客戶端 并且接收到原生客戶端的回復
  Future<String> sendMessage() async {
    String reply = await basicChannel.send('this is flutter');
    print("receive reply msg from native:$reply");
    return reply;
  }

  //接收原生消息 并發送回復
  void receiveMessage() async {
    basicChannel.setMessageHandler((msg) async {
      print("receive from Android:$msg");
      return "get native message";
    });

上面例子中用到的編解碼器為StandardMessageCodec ,例子中通信都是String,用StringCodec也可以。

以上就是Flutter提供三種platform和dart端的消息通信方式。

以上相關代碼demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379