Flutter与Native通信(一)MethodChannel

07/23/2022 10:03 上午 posted in  Flutter

flutter可以与native之间进行通信,帮助我们使用native提供的能力。通信是双向的,我们可以从Native层调用flutter层的dart代码,同时也可以从flutter层调用Native的代码。我们需要使用Platform Channels APIs进行通信,主要包括下面三种:

  • [MethodChanel]:用于传递方法调用(method invocation)
  • [EventChannel]:用于事件流的发送(event streams)
  • [MessageChannel]:用于传递字符串和半结构化的消息

其中最常用的是MethodChanel,MethodChanel的使用与在Android的JNI调用非常类似,但是MethodChanel更加简单,而且相对于JNI的同步调用MethodChanel的调用是异步的:

1. MethodChanel的基本流程

从flutter架构图上可以看到,flutter与native的通信发生在Framework和Engine之间,framewrok内部会将MethodChannel以BinaryMessage的形式与Engine进行数据交换。关于BinaryMessage在这里不做过多介绍,主要以介绍Channel的使用为主。

我们先看一下MethodChanel使用的基本流程:

flutter调用native

  1. [native] 使用MethodChannel#setMethodCallHandler注册回调
  2. [flutter] 通过MethodChannel#invokeMethod发起异步调用
  3. [native] 调用native方法通过Result#success 返回Result,出错时返回error
  4. [flutter] 收到native返回的Result

native调用flutter

与flutter调用native的顺序完全一致,只是[native]与[flutter]角色反调

2. 代码实现

flutter调用native

首先在flutter端实现以下功能:

  • 创建MethodChannel,并注册channel名,一般使用“包名/标识”作为channel名
  • 通过invokeMethod发起异步调用,invokeMethod接受两个参数:
    • method:调用的native方法名
    • arguments:nativie方法参数,有多个参数时需要以map形式指定
import 'package:flutter/services.dart';
 
class _MyHomePageState extends State<MyHomePage> {
  static const MethodChannel _channel = const MethodChannel('com.example.methodchannel/interop');
 
  static Future<dynamic> get _list async {
    final Map params = <String, dynamic> {
      'name': 'my name is hoge',
      'age': 25,
    };
    final List<dynamic> list = await _channel.invokeMethod('getList', params);
    return list;
  }
 
  @override
  initState() {
    super.initState();
 
    // Dart -> Platforms
    _list.then((value) => print(value));
  }

在native(android)端实现以下功能

  • 创建MethodChannel,必须跟flutter中使用相同的注册字符串
  • 设置MethodCallHander,methodCall中传递来自flutter的参数
  • 通过result返回给flutter结果
class MainActivity: FlutterActivity() {
    companion object {
        private const val CHANNEL = "com.example.methodchannel/interop"
        private const val METHOD_GET_LIST = "getList"
    }
 
    private lateinit var channel: MethodChannel
 
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
 
        channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        channel.setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result ->
            if (methodCall.method == METHOD_GET_LIST) {
                val name = methodCall.argument<String>("name").toString()
                val age = methodCall.argument<Int>("age")
                Log.d("Android", "name = ${name}, age = $age")
 
                val list = listOf("data0", "data1", "data2")
                result.success(list)
            }
            else
                result.notImplemented()
        }
    }

因为结果返回是异步的,所以既可以像上面代码那样在MethodCallHandler里通过result.success返回结果,也也可以先保存result的引用,在之后的某个时间点再调用sucess,但需要特别注意的是无论何时调用result.sucess,必须确保其在UI线程进行:

@UiThread void success(@Nullable Object result)

native调用flutter

android调用flutter的代码实现与flutter调用android是类似的,只不过要注意所以的调用都要在UI线程进行。

先实现android部分的代码:

channel.invokeMethod("callMe", listOf("a", "b"), object : MethodChannel.Result {
    override fun success(result: Any?) {
        Log.d("Android", "result = $result")
    }
    override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
        Log.d("Android", "$errorCode, $errorMessage, $errorDetails")
    }
    override fun notImplemented() {
        Log.d("Android", "notImplemented")
    }
})
result.success(null)

flutte部分则主要实现MethodCallHandler的注册:

Future<dynamic> _platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case 'callMe':
        print('call callMe : arguments = ${call.arguments}');
        return Future.value('called from platform!');
        //return Future.error('error message!!');
      default:
        print('Unknowm method ${call.method}');
        throw MissingPluginException();
        break;
    }
  }
 
  @override
  initState() {
    super.initState();
 
    // Platforms -> Dart
    _channel.setMethodCallHandler(_platformCallHandler);
  }