如需轉載,請註明出處:Flutter學習筆記(29)--Flutter如何與native進行通信 前言:在我們開發Flutter項目的時候,難免會遇到需要調用native api或者是其他的情況,這時候就需要處理Flutter與native的通信問題,一般常用的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), ), ); } }