1. 程式人生 > >Flutter學習筆記(29)--Flutter如何與native進行通訊

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

如需轉載,請註明出處: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通訊的全部內容了,理解了以後其實很簡單,上面的內容有一些我個人的理解,更深一層的還需要繼續挖掘!