Flutter 与原生通信总结

一、通信方式介绍

  • MethodChannel 与原生互相进行方法调用,用于方法调用(双向)
  • BasicMessageChannel 与原生互相发送消息,用于数据传输(双向)
  • EventChannel 原生发送消息,Flutter 接收,用于数据流通信(单向)

二、三种方式使用对比

以下主要针对 MethodChannel 这种常用通信方式做详细分析。

三、MethodChannel 通信类设计

  • 以下为 Android 端类图,对于 Dart 侧,基本一致,不细述


关于 MethodChannel 方式相互调用的流程简图如下:


  • invokeMethod() 发送消息,将 MethodCall 进行编码传输,Native 端是编码为 ByteBuffer ,Dart 侧则是编码为 ByteData
  • Native 侧收到 Dart 消息后,底层通过 FlutterJNI 实现,上层通过 BinaryMessageHandler.onMessage() 回调监听,将收到的 ByteBuffer 解码为 MethodCall ;
  • Native 根据方法传参做逻辑处理后,若要回复消息/回传返回值,则通过 BinaryReply result 进行,最终传输的数据会被编码为 ByteBuffer 回信过去,方法也是表达得极为明确:encodeEnvelope() 回信。

消息的编码与解码 MessageCodec,Dart 和 native 侧分别有四种编码方式,两端都是一致的。


备注:点击查看 类型对应关系

四、MethodChannel 方法调用示例

无论是 Dart 调用 Android,还是 Android 调用 Dart,均使用 invokeMethod()触发调用 以及 setMethodCallHandler() 监听调用事件进行。

1. Dart 调用 Native 方法

1)调用 Native 方法getMessageMethodChannel.invokeMethod('getMessage')
2)Native 监听方法调用并执行对应方法 Native 逻辑
3)如果需要回传 Native 数据给 Dart,则使用:result.success() 传递数据,该结果直接从上述 invokeMethod() 方法可获取到。

class MainActivity: FlutterActivity() {

   val CHANNEL = "native.call";

    var count = 0;

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        channel.setMethodCallHandler { call, result ->
            // Note: this method is invoked on the main thread.
            if (call.method == "showToast") {
                Toast.makeText(this, "Hello flutter, I'm Android !", Toast.LENGTH_LONG).show();
            } else if (call.method == "getMessage") {
                // 一次会话通道只能回复一次,不能回复多次,否则抛异常:java.lang.IllegalStateException: Reply already submitted
                result.success("Android: ${count++}")
            }

            Log.e("DartCall", Thread.currentThread().name)

            for (i in 0..5) {
                channel.invokeMethod("callDart", "Hell dart, $i")
            }
        }
    }
}

2. Native 调用 Dart 方法

  • 在 initState() 方法中,注册 native 调用 dart 方法监听:platform.setMethodCallHandler((MethodCall call) { }
  • 处理 native 方法传参。
  // 1. Android 调用 Dart 方法:
  for (i in 0..5) {
     channel.invokeMethod("callDart", "Hell dart, $i")
  }

  // 2. Dart 侧注册 Native 事件调用监听
  @override
  void initState() {
    super.initState();
    registerNativeCall();
  }

  dynamic nativeArgs;
  void setNativeArgs(dynamic args) {
    nativeArgs = args;
    print('$nativeArgs');
  }
  
  void registerNativeCall() {
    platform.setMethodCallHandler((MethodCall call) {
      if (call.method == 'callDart') {
        setNativeArgs(call.arguments);
      }
    });
  }

3. 注意事项

1)Dart 调用 Native 方法,Native 侧可以直接使用 MethodChannel.Result 回传数据给 Dart,但是一次通信过程中,result.success() 方法只能调用一次,否则就会出现以下异常:

E/DartMessenger(19357): Uncaught exception in binary message listener
E/DartMessenger(19357): java.lang.IllegalStateException: Reply already submitted
E/DartMessenger(19357):     at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:149)
E/DartMessenger(19357):     at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:253)
E/DartMessenger(19357):     at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:91)
E/DartMessenger(19357):     at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:724)
E/DartMessenger(19357):     at android.os.MessageQueue.nativePollOnce(Native Method)
E/DartMessenger(19357):     at android.os.MessageQueue.next(MessageQueue.java:326)
E/DartMessenger(19357):     at android.os.Looper.loop(Looper.java:160)
E/DartMessenger(19357):     at android.app.ActivityThread.main(ActivityThread.java:6898)
E/DartMessenger(19357):     at java.lang.reflect.Method.invoke(Native Method)
E/DartMessenger(19357):     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
E/DartMessenger(19357):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

解决方法,如果要持续传递 Native 数据给 Dart,则直接使用 invokeMethod() 方法,Dart 侧监听到自己方法调用,再做处理。

4. 完整示例


import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class NativeCallDemoWidget extends StatefulWidget {
  
  @override
  State<StatefulWidget> createState() => NativeCallState();
  
}

class NativeCallState extends State<NativeCallDemoWidget> {
  
  static const platform = MethodChannel('native.call');

  String nativeMessage = 'Hello: ';

  Future<void> showToast() async {
    await platform.invokeMethod('showToast');
  }

  Future<String> getMessage() async {
    String result = await platform.invokeMethod('getMessage');

    // 更新页面数据
    setState(() {
      nativeMessage += result;
    });

    return result;
  }

  dynamic nativeArgs;
  void setNativeArgs(dynamic args) {
    nativeArgs = args;
    print('$nativeArgs');
  }

  void registerNativeCall() {
    platform.setMethodCallHandler((MethodCall call) {
      if (call.method == 'callDart') {
        setNativeArgs(call.arguments);
      }
    });
  }

  @override
  void initState() {
    super.initState();
    registerNativeCall();
  }
  
  @override
  Widget build(BuildContext context) {
    Widget body = Column(
      children: [
        FlatButton(
          child: Text('Show Native Toast'),
          onPressed: () {
            showToast();
          },
          shape: RoundedRectangleBorder(
              side: BorderSide()
          ),
        ),

        FlatButton(
          child: Text('getMessage from Native'),
          onPressed: () {
            getMessage();
          },
          shape: RoundedRectangleBorder(
              side: BorderSide()
          ),
        ),

        Text(nativeMessage)
      ],
    );

    return new MaterialApp(
        title: 'Flutter 与 Native 通信方式',
        theme: new ThemeData(
            primarySwatch: Colors.blue,
            primaryColor: Colors.blue
        ),
        home: new Scaffold(
          appBar: new AppBar(
            title: new Text(
            'Flutter 与 Native 通信方式'
          ),
        ),
          body: body,
      ),
    );
  }
  
}

六、相关文档

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

推荐阅读更多精彩内容