(八)Flutter 和 Native 之间的通信详解

前言

在实际的开发中通常需要 Flutter 调用 Native 的功能,或者 Native 调用 Flutter 的功能

它们之间的通信主要是通过 Platform Channel 来实现的, 主要有 3channel :

  • MethodChannel 用于传递方法调用
  • EventChannel 用于数据流(event streams)的通信
  • BasicMessageChannel 用于传递字符串和半结构化的信息

下图以 MethodChannel 为例, 展示了 FlutterNative 之间的消息传递:

在这里插入图片描述

为了应用的流畅度, 能够及时响应用户的操作, FlutterNative 之间消息和响应的传递都是异步的, 但是调用 channel api 的时候需要在 主线程 中调用

Platform Channel 支持的数据类型

Platform Channel 通过标准的消息编解码器来为我们在 发送接收 数据时自动 序列化反序列化

编解码支持的数据类型有:

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int(if 32 bits not enough) java.lang.Long NSNumber numberWithLong:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

MethodChannel

Flutter 获取 手机电量 为例, 在 Flutter 界面中要想获取 Android/iOS 的电量, 首先要在 Native 编写获取电量的功能, 供 Flutter 来调用

Native 端代码

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "com.example.flutter_battery/battery";
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
    new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
            (call, result) -> {
              // 在主线程中执行
              if (call.method.equals("getBatteryLevel")) {
                // 获取电量
                int batteryLevel = fetchBatteryLevel();
                if (batteryLevel != -1) {
                  // 将电量返回给 Flutter 调用
                  result.success(batteryLevel);
                } else {
                  result.error("UNAVAILABLE", "Battery level not available.", null);
                }
              } else {
                result.notImplemented();
              }
            });
  }

  // 获取电量的方法
  private int fetchBatteryLevel() {
    int batteryLevel;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
      batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
    } else {
      Intent intent = new ContextWrapper(getApplicationContext()).
              registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
      batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
              intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    }

    return batteryLevel;
  }
}

在 Native 代码中, 我们新建了一个 fetchBatteryLevel 函数来获取电量, 然后 new 一个 MethodChannel 对象

这里需要注意该构造函数的第二个参数 CHANNEL, 这个字符串在稍后的 Flutter 中也要用到

最后为 MethodChannel 设置函数调用处理器 MethodCallHandler, 也就是 Flutter 调用 Native 函数的时候会回调这个MethodCallHandler

Flutter 端代码

class _MyHomePageState extends State<MyHomePage> {
  // 构造函数参数就是上面 Android 的 CHANNEL 常量
  static const methodChannelBattery = const MethodChannel('com.example.flutter_battery/battery');

  String _batteryLevel = 'Unknown battery level.';

  Future<void> _getBatteryLevel() async {
    String batteryLevel;
    try {
      // invokeMethod('getBatteryLevel') 会回调 MethodCallHandler
      final int result = await methodChannelBattery.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    } on MissingPluginException catch (e) {
      batteryLevel = "plugin undefined";
    }
    setState(() {
      _batteryLevel = batteryLevel;
    });
  }


  @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(
                      'GetBatteryFromNative',
                      style: TextStyle(fontSize: 12),
                    ),
                    onPressed: _getBatteryLevel,
                  ),
                  Padding(
                    padding: EdgeInsets.only(left: 10),
                    child: Text(_batteryLevel),
                  )
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

点击 Flutter 界面上的按钮就可以获取到 Android 手机里的电量了:

0

MethodChannel 除了使用实现 Flutter 调用 Native 函数, 也可以 Native 调用 Flutter 函数

首先要在 Native 端调用 invokeMethod 方法, 指定你要调用哪个 Flutter 方法:

@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
    if (call.method.equals("getBatteryLevel")) {
        int batteryLevel = fetchBatteryLevel();

        if (batteryLevel != -1) {
            result.success(batteryLevel);
        } else {
            result.error("UNAVAILABLE", "Battery level not available.", null);
        }
    } else {
        result.notImplemented();
    }
    
    // Native 调用 Flutter 的 getFlutterContent 函数
    channel.invokeMethod("getFlutterContent", null, new MethodChannel.Result() {
        @Override
        public void success(Object o) {
            Log.e("BatteryPlugin", "Dart getFlutterContent() result : " + o);
        }

        @Override
        public void error(String s, String s1, Object o) {
            Log.e("BatteryPlugin", "Dart getFlutterContent() error : " + s);
        }

        @Override
        public void notImplemented() {
            Log.e("BatteryPlugin", "Dart getFlutterContent() notImplemented");

        }
    });
}

然后在 Flutter 中设置 MethodChannelMethodCallHandler, 也就是说 Native 调用了 invokeMethod 方法后, Flutter 怎么处理:

void initState() {
    super.initState();
    methodChannelBattery.setMethodCallHandler(batteryCallHandler);
}

Future<dynamic> batteryCallHandler(MethodCall call) async {
    switch (call.method) {
      case "getFlutterContent":
        return "This is FlutterContent";
    }
}

上面代码的主要意思是, 当我们点击按钮调用 Native 里的函数获取电量, 然后在 Native 中立马调用 Flutter 中的 getFlutterContent 函数

然后控制台就会输出, 我们从 Flutter getFlutterContent() 的返回值:

Dart getFlutterContent() result : This is FlutterContent

EventChannel

EventChannel 适用于事件流的通信, 例如 Native 需要频繁的发送消息给 Flutter, 比如监听网络状态, 蓝牙设备等等然后发送给 Flutter

下面我们以一个案例来介绍 EventChannel 的使用, 该案例是在 Native 中每秒发送一个事件给 Flutter:

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;
    }
}

Flutter 端代码

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"),
                  )
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

效果如下图所示:

EventChannel

BasicMessageChannel

BasicMessageChannel 更像是一个消息的通信, 如果仅仅是简单的通信而不是调用某个方法或者是事件流, 可以使用 BasicMessageChannel

BasicMessageChannel 也可以实现 FlutterNative 的双向通信, 下面的示例图就是官方的例子:

image

点击 Native FAB 通知 Flutter 更新, 点击 Flutter FAB 通知 Native 更新

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),
      ),
    );
  }
}

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);
}

关于 FlutterNative 之间的通信就介绍到这里了. 总而言之, 如果通信需要方法调用可以使用 MethodChannel, 通信的时候用到数据流则使用 EventChannel, 如果仅仅是消息通知则可以使用 BasicMessageChannel.

Reference

https://flutter.dev/docs/development/platform-integration/platform-channels
https://juejin.im/post/5b84ff6a6fb9a019f47d1cc9
https://juejin.im/post/5b4c3c9a5188251ac446d915
https://juejin.im/post/5b3ae6b96fb9a024ba6e0dbb

联系我

所有关于 Retrofit 的使用案例都在我的 AndroidAll GitHub 仓库中。该仓库除了 Retrofit,还有其他 Android 其他常用的开源库源码分析,如「RxJava」「Glide」「LeakCanary」「Dagger2」「Retrofit」「OkHttp」「ButterKnife」「Router」等。除此之外,还有完整的 Android 程序员所需要的技术栈思维导图,欢迎享用。

下面是我的公众号,干货文章不错过,有需要的可以关注下,有任何问题可以联系我:

公众号: chiclaim
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容