Flutter學習筆記(29)--Flutter如何與native進行通訊

  • 2019 年 10 月 12 日
  • 筆記

如需轉載,請註明出處:Flutter學習筆記(29)--Flutter如何與native進行通訊

前言:在我們開發Flutter項目的時候,難免會遇到需要調用native api或者是其他的情況,這時候就需要處理Flutter與native的通訊問題,一般常用的Flutter與native的通訊方式有3中。

1.MethodChannel:Flutter端向native端發送通知,通常用來調用native的某一個方法。

2.EventChannel:用於數據流的通訊,有監聽功能,比如電量變化後直接推送給Flutter端。

3.BasicMessageChannel:用於傳遞字元串或半結構體的數據。

接下來具體看一下每種通訊方式的使用方法!

  • MethodChannel

先來整體說一下邏輯思想吧,這樣能更容易理解一些,如果想要實現Flutter與native通訊,首先要建立一個通訊的通道,通過一個通道標識來進行匹配,匹配上了之後Flutter端通過invokeMethod調用方法來發起一個請求,在native端通過onMethodCall進行匹配請求的key,匹配上了就處理對應case內的邏輯!!!整體來看,我感覺有點EventBus的意思呢,就像是一條事件匯流排。。。

第一步:實現通訊插件Plugin-native端

由於一個項目中可能會需要很多Flutter與native的通訊,所以我這裡是將測試的插件封裝到一個類裡面了,然後在MainActivity裡面的onCreate進行註冊

package com.example.flutter_demo;    import android.content.Context;    import io.flutter.plugin.common.MethodCall;  import io.flutter.plugin.common.MethodChannel;  import io.flutter.plugin.common.PluginRegistry;    public class TestPlugin implements MethodChannel.MethodCallHandler {      public static String CHANNELNAME = "channel_name";//每一個通訊通道的唯一標識,在整個項目內唯一!!!      private static MethodChannel methodChannel;      private Context context;        public TestPlugin(Context context) {          this.context = context;      }        public static void registerWith(PluginRegistry.Registrar registrar){          methodChannel = new MethodChannel(registrar.messenger(),CHANNELNAME);          TestPlugin instance = new TestPlugin(registrar.activity());          methodChannel.setMethodCallHandler(instance);      }        @Override      public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {          if (methodCall.method.equals("method_key")){              result.success("what is up man???");          }      }  }

 

註:CHANNELNAME-->上面說過了,由於項目內會有很多的通訊,所以我們定義的Channel必須是唯一的!!!!

TestPlugin實現MethodChannel.MethodCallHandler,定義一個對外暴露的註冊方法registerWith,因為我們需要在MainActivity進行註冊,在registerWith方法內初始化MethodChannel

接下來我們看一下onMethodCall方法,這個方法在Flutter發起請求時被調用,方法內有兩個參數,一個methodCall和一個result,我們分別來說一下這兩個參數:

methodCall:其中當前請求的相關資訊,比如匹配請求的key

result:用於給Flutter返回數據,有3個方法,result.success(成功調用)、result.erro(失敗調用)、result.notImplemented(方法沒有實現調用)

第二步:註冊通訊插件Plugin-native端

package com.example.flutter_demo;    import android.os.Bundle;  import io.flutter.app.FlutterActivity;  import io.flutter.plugins.GeneratedPluginRegistrant;    public class MainActivity extends FlutterActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      GeneratedPluginRegistrant.registerWith(this);      TestPlugin.registerWith(this.registrarFor(TestPlugin.CHANNELNAME));    }  }

 

註冊這塊我感覺作用是起到了一個橋樑的作用,通過註冊將插件和Flutter內定義的CHANNEL關聯了起來。

第三步:Flutter內發起通訊請求-flutter端

import 'package:flutter/material.dart';  import 'package:flutter/services.dart';    void main() => runApp(MyApp());    class MyApp extends StatefulWidget{    @override    State<StatefulWidget> createState() {      // TODO: implement createState      return new MyAppState();    }    }    class MyAppState extends State<MyApp> {    var _textContent = 'welcome to flutter word';      Future<Null> _changeTextContent() async{      //channel_name每一個通訊通道的唯一標識,在整個項目內唯一!!!      const platfom = const MethodChannel('channel_name');      try {        //method_key是插件TestPlugin中onMethodCall回調匹配的key        String resultValue = await platfom.invokeMethod('method_key');        setState(() {          _textContent = resultValue;        });      }on PlatformException catch (e){        print(e.toString());      }    }      @override    Widget build(BuildContext context) {      // TODO: implement build      return new MaterialApp(        theme: new ThemeData(          primaryColor: Colors.white,        ),        debugShowCheckedModeBanner: false,        title: 'demo',        home: new Scaffold(          appBar: new AppBar(            title: new Text('Demo'),            leading: Icon(Icons.menu,size: 30,),            actions: <Widget>[              Icon(Icons.search,size: 30,)            ],          ),          body: new Center(            child: new Text(_textContent),          ),          floatingActionButton: new FloatingActionButton(onPressed: _changeTextContent,child: new Icon(Icons.adjust),),        ),      );    }  }

 

這裡的功能就是頁面中央有一個text,通過點擊一個按鈕,發起通訊請求,通訊成功在就收到native返回的數據後將text的文案修改。

我們看一下最終的效果:

                

MethodChannel通訊是雙向的,也就是說,Flutter端可以向native發起通訊,native也可以向Flutter端發起通訊,本質上就是反過來調用一下,原理上是同一個意思,具體的程式碼就不在這裡寫了,需要的話可以自行百度一下!

  • EventChannel

EventChannel的使用我們也以官方獲取電池電量的demo為例,手機的電池狀態是不停變化的。我們要把這樣的電池狀態變化由Native及時通過EventChannel來告訴Flutter。這種情況用之前講的MethodChannel辦法是不行的,這意味著Flutter需要用輪詢的方式不停調用getBatteryLevel來獲取當前電量,顯然是不正確的做法。而用EventChannel的方式,則是將當前電池狀態"推送"給Flutter。

第一步:MainActivity內註冊EventChannel,並提供獲取電量的方法-native端

public class EventChannelPlugin implements EventChannel.StreamHandler {        private Handler handler;      private static final String CHANNEL = "com.example.flutter_battery/stream";      private int count = 0;        public static void registerWith(PluginRegistry.Registrar registrar) {          // 新建 EventChannel, CHANNEL常量的作用和 MethodChannel 一樣的          final EventChannel channel = new EventChannel(registrar.messenger(), CHANNEL);          // 設置流的處理器(StreamHandler)          channel.setStreamHandler(new EventChannelPlugin());      }        @Override      public void onListen(Object o, EventChannel.EventSink eventSink) {          // 每隔一秒數字+1          handler = new Handler(message -> {              // 然後把數字發送給 Flutter              eventSink.success(++count);              handler.sendEmptyMessageDelayed(0, 1000);              return false;          });          handler.sendEmptyMessage(0);        }        @Override      public void onCancel(Object o) {          handler.removeMessages(0);          handler = null;          count = 0;      }  }

 

 

其中onCancel代表對面不再接收,這裡我們應該做一些clean up的事情。而 onListen則代表通道已經建好,Native可以發送數據了。注意onListen裡帶的EventSink這個參數,後續Native發送數據都是經過EventSink的。

第二步:同MethodChannel一樣,發起通訊請求

class _MyHomePageState extends State<MyHomePage> {    // 創建 EventChannel    static const stream = const EventChannel('com.example.flutter_battery/stream');      int _count = 0;      StreamSubscription _timerSubscription;      void _startTimer() {      if (_timerSubscription == null)         // 監聽 EventChannel 流, 會觸發 Native onListen回調        _timerSubscription = stream.receiveBroadcastStream().listen(_updateTimer);    }      void _stopTimer() {      _timerSubscription?.cancel();      _timerSubscription = null;      setState(() => _count = 0);    }      void _updateTimer(dynamic count) {      print("--------$count");      setState(() => _count = count);    }      @override    void dispose() {      super.dispose();      _timerSubscription?.cancel();      _timerSubscription = null;    }      @override    Widget build(BuildContext context) {      return Scaffold(        appBar: AppBar(          title: Text(widget.title),        ),        body: Container(          margin: EdgeInsets.only(left: 10, top: 10),          child: Center(            child: Column(              children: [                Row(                  children: <Widget>[                    RaisedButton(                      child: Text('Start EventChannel',                          style: TextStyle(fontSize: 12)),                      onPressed: _startTimer,                    ),                    Padding(                        padding: EdgeInsets.only(left: 10),                        child: RaisedButton(                          child: Text('Cancel EventChannel',                              style: TextStyle(fontSize: 12)),                          onPressed: _stopTimer,                        )),                    Padding(                      padding: EdgeInsets.only(left: 10),                      child: Text("$_count"),                    )                  ],                )              ],            ),          ),        ),      );    }  }

 

 

整體說明一下:Flutter端通過stream.receiveBroadcastStream().listen監聽native發送過來的數據,native端通過eventSink.success(++count)不斷的將數據返回給Flutter端,這樣就實現了我們想要的實時監聽的效果了!

  • BasicMessageChannel

其實他就是一個簡版的MethodChannel,也可以說MethodChannel是基於BasicMessageChannel實現的,BasicMessageChannel只是進行通訊,更通俗的理解就是兩端發通知,但是不需要進行方法匹配。

第一步:初始化及註冊-native

@Override  protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);        // 省略其他程式碼...        messageChannel = new BasicMessageChannel<>(flutterView, CHANNEL, StringCodec.INSTANCE);      messageChannel.          setMessageHandler(new MessageHandler<String>() {              @Override              public void onMessage(String s, Reply<String> reply) {                  // 接收到Flutter消息, 更新Native                  onFlutterIncrement();                  reply.reply(EMPTY_MESSAGE);              }          });        FloatingActionButton fab = findViewById(R.id.button);      fab.setOnClickListener(new View.OnClickListener() {          @Override          public void onClick(View v) {              // 通知 Flutter 更新              sendAndroidIncrement();          }      });  }    private void sendAndroidIncrement() {      messageChannel.send(PING);  }    private void onFlutterIncrement() {      counter++;      TextView textView = findViewById(R.id.button_tap);      String value = "Flutter button tapped " + counter + (counter == 1 ? " time" : " times");      textView.setText(value);  }

 

第二步:Flutter端發起通訊-flutter

class _MyHomePageState extends State<MyHomePage> {    static const String _channel = 'increment';    static const String _pong = 'pong';    static const String _emptyMessage = '';    static const BasicMessageChannel<String> platform =        BasicMessageChannel<String>(_channel, StringCodec());      int _counter = 0;      @override    void initState() {      super.initState();      // 設置消息處理器      platform.setMessageHandler(_handlePlatformIncrement);    }      // 如果接收到 Native 的消息 則數字+1    Future<String> _handlePlatformIncrement(String message) async {      setState(() {        _counter++;      });      // 發送一個空消息      return _emptyMessage;    }      // 點擊 Flutter 中的 FAB 則發消息給 Native    void _sendFlutterIncrement() {      platform.send(_pong);    }      @override    Widget build(BuildContext context) {      return Scaffold(        appBar: AppBar(          title: Text('BasicMessageChannel'),        ),        body: Container(            child: Column(          crossAxisAlignment: CrossAxisAlignment.start,          children: <Widget>[            Expanded(              child: Center(                child: Text(                    'Platform button tapped $_counter time${_counter == 1 ? '' : 's'}.',                    style: const TextStyle(fontSize: 17.0)),              ),            ),            Container(              padding: const EdgeInsets.only(bottom: 15.0, left: 5.0),              child: Row(                children: <Widget>[                  Image.asset('assets/flutter-mark-square-64.png', scale: 1.5),                  const Text('Flutter', style: TextStyle(fontSize: 30.0)),                ],              ),            ),          ],        )),        floatingActionButton: FloatingActionButton(          onPressed: _sendFlutterIncrement,          child: const Icon(Icons.add),        ),      );    }  }

 

總結:以上就是Flutter和native通訊的全部內容了,理解了以後其實很簡單,上面的內容有一些我個人的理解,更深一層的還需要繼續挖掘!

Tags: