Flutter Platform Channel - MethodChannel 使用

1,048 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第 7 天,点击查看活动详情

一、前言

Flutter 使用了一个灵活的系统,允许您调用特定平台的 API,无论在 Android 上的 JavaKotlin 代码中,还是 iOS 上的 ObjectiveCSwift 代码中均可用。

Flutter 平台特定的 API 支持不依赖于代码生成,而是依赖于灵活的消息传递的方式:

  • 应用的 Flutter 部分通过平台通道(platform channel)将消息发送到其应用程序的所在的宿主(iOSAndroid)。
  • 宿主监听的平台通道,并接收该消息。然后它会调用特定于该平台的 API(使用原生编程语言) - 并将响应发送回客户端,即应用程序的 Flutter 部分。

二、Platform Channel 的 3 中方式

作为一个UI框架,Flutter 提供了三种通道来和原生平台通信。

  • BasicMessageChannel:它提供类似于 BinaryMessages 的基本消息传递服务,但可自定义消息编解码器,支持发送字符串或半结构化消息。用于使用指定的编解码器对消息进行编码和解码,属于双向通信,可以 Native 端主动调用,也可以 Flutter 主动调用
  • MethodChannel:它使用异步方法调用的方式进行平台通信。Flutter 与 Native 端相互调用,调用后可以返回结果,可以 Native 端主动调用,也可以 Flutter 主动调用,属于双向通信。此方式为最常用的方式, Native 端调用需要在主线程中执行。
  • EventChannel:它使用事件流的方式进行平台通信。Native 端主动发送数据给 Flutter,通常用于状态的监听,比如网络变化、传感器数据等。

平台通道在客户端和宿主之间传递消息,如图所示:

image.png

平台通道数据类型支持和解码器:

DartAndroidiOS
nullnullnil (NSNull when nested)
booljava.lang.Booleanint
intjava.lang.IntegerNSNumber numberWithInt
int, if 32 bits not enoughjava.lang.LongNSNumber numberWithLong:
int, if 64 bits not enoughjava.math.BigIntegerFlutterStandardBigInteger
doublejava.lang.DoubleNSNumber numberWithDouble:
Stringjava.lang.StringNSString
Uint8Listbyte[]FlutterStandardTypedData typedDataWithBytes:
Int32Listint[]FlutterStandardTypedData typedDataWithInt32:
Int64Listlong[]FlutterStandardTypedData typedDataWithInt64:
Float64Listdouble[]FlutterStandardTypedData typedDataWithFloat64:
Listjava.util.ArrayListNSArray
Mapjava.util.HashMapNSDictionary

三、 MethodChannel

例一、Flutter 端通过调用 Native 方法,然后 Native 返回给 Flutter 数据进行展示。

1、Flutter 端

Flutter 创建 MethodChannel 通道,用于与 Native 通信:

class PluginTest {
  factory PluginTest() {
    if (_singleton == null) {
      const MethodChannel methodChannel =
          MethodChannel('flutter.billionbottle.com/method-channel');
      const EventChannel eventChannel =
          EventChannel('flutter.billionbottle.com/event-channel');
      _singleton = PluginTest._(
        methodChannel,
        eventChannel,
      );
    }
    return _singleton!;
  }

  PluginTest._(
    this._methodChannel,
    this._eventChanel,
  );

  final MethodChannel _methodChannel;
  final EventChannel _eventChanel;
  static PluginTest? _singleton;

  Future<Map<dynamic, dynamic>?> sendDataToNative(
    Map<String, dynamic> params,
  ) async {
    final Map<dynamic, dynamic>? result = await _methodChannel.invokeMapMethod(
      'sendDataToNative',
      params,
    );
    return result;
  }
}

flutter.billionbottle.com/method-channelMethodChannel 的名称, Native 端要与之保持对应。

发送消息并且接收来自 Native 的结果:

class PluginDemoPage extends StatefulWidget {
  PluginDemoPage({super.key});

  @override
  State<PluginDemoPage> createState() => _PluginDemoPageState();
}

class _PluginDemoPageState extends State<PluginDemoPage> {
  String? nativeResult;

  Future<void> sendDataToNative() async {
    final PluginTest plugin = PluginTest();
    final Map<dynamic, dynamic>? result = await plugin.sendDataToNative(
      <String, dynamic>{
        'name': 'tom',
        'age': '20',
      },
    );
    setState(() {
      nativeResult = '$result';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Scaffold(
        appBar: AppBar(
          title: Text('plugin Demo'),
          centerTitle: true,
        ),
        body: Column(
          children: <Widget>[
            OutlinedButton(
              onPressed: sendDataToNative,
              child: Text('sendDataToNative'),
            ),
            Text('Native 返回的数据:$nativeResult'),
          ],
        ),
      ),
    );
  }
}
  • 第一个参数标识 method ,方法名称, Native 端会解析次参数;
  • 第二个参数表示传递到 Native 端的参数,类型任意,多个参数通常使用 Map;
  • 返回 Future 类型,Native 端返回的数据。
2、Android 端
class MethodChannelTest(messenger: BinaryMessenger) :MethodChannel.MethodCallHandler{
    private val channelName = "flutter.billionbottle.com/method-channel"
    private val channel:MethodChannel

    init {
        channel = MethodChannel(messenger, channelName)
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        if(call.method=="sendDataToNative") {
            val name = call.argument("name") as String?
            val age = call.argument("age") as String?
            val map = mapOf("name" to "$name", "age" to "$age")
            result.success(map)
        }
    }
}
  • call.method 字符串就是 invokeMethod 方法传入的 method;
  • call.argument 是 invoke 传入的参数,由于 Flutter 端传入的是 Map ,所以上面按照 map 解析;
  • result.success() 是返回给 Flutter 的结果。

然后再 MainActivity 中进行添加如下代码:

class MainActivity: FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannelTest(flutterEngine.dartExecutor.binaryMessenger)
    }
}

当我们运行起来,点击按钮时,会看到如下效果:

image.png

相关代码改动可以查看本次 Git 提交记录

例二、Native 端主动调用 Flutter 方法,然后 Native 返回给 Flutter 数据进行展示。

1、Flutter 端

首先修改 PluginTest 中的代码:

typedef MethodCallback = void Function(dynamic result);
factory PluginTest() {
    if (_singleton == null) {
      final MethodChannel methodChannel =
          MethodChannel('flutter.billionbottle.com/method-channel')
            // setMethodCallHandler
            ..setMethodCallHandler((MethodCall call) async {
              final String callMethodName = call.method;
              if (_callbacks[callMethodName] != null) {
                return _callbacks[callMethodName](call.arguments);
              }
            });

      const EventChannel eventChannel =
          EventChannel('flutter.billionbottle.com/event-channel');
      _singleton = PluginTest._(
        methodChannel,
        eventChannel,
      );
    }
    return _singleton!;
  }
  
  static final Map<String, dynamic> _callbacks = <String, MethodCallback>{};

  void registerCallback(String callMethodName, MethodCallback callback) {
    _callbacks[callMethodName] = callback;
  }

  void unregisterCallback(String callMethodName) {
    _callbacks.remove(callMethodName);
  }

在上面代码我们修改了 MethodChannel 添加 setMethodCallHandler,然后再添加 registerCallback 和 registerCallback 方法,方便添加回调方法。

然后修改 PluginDemoPage 中添加如下代码:

  String? receiverResult;

  @override
  void initState() {
    super.initState();
    final PluginTest plugin = PluginTest();
    plugin.registerCallback('timer', receiverNative);
  }

  @override
  void dispose() {
    super.dispose();
    final PluginTest plugin = PluginTest();
    plugin.unregisterCallback('timer');
  }

  void receiverNative(dynamic result) {
    setState(() {
      receiverResult = '$result';
    });
  }

在上述代码中我们在 initState 中注册方法,在 dispose 解绑回调方法,程序启动进入当前页面会接受来自 Native 的执行结果。

2、Android 端

我们首先修改 MethodChannelTest 代码:

class MethodChannelTest(var activity: Activity, messenger: BinaryMessenger) :MethodChannel.MethodCallHandler{
    private val channelName = "flutter.billionbottle.com/method-channel"
    private val channel:MethodChannel
    private var count = 0

    init {
        channel = MethodChannel(messenger, channelName)
        channel.setMethodCallHandler(this)
        starTimer()
    }

    private fun starTimer() {
        var timer = Timer().schedule(timerTask {
            activity.runOnUiThread {
                var map = mapOf("count" to count++)
                channel.invokeMethod("timer", map)
            }
        }, 0, 1000)
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        if(call.method=="sendDataToNative") {
            val name = call.argument("name") as String?
            val age = call.argument("age") as String?
            val map = mapOf("name" to "$name", "age" to "$age")
            result.success(map)
        }
    }
}

在上述代码中修改 MethodChannelTest 构造方法,添加 activity 入参,因为 Android 中发送数据要在主线程中调用,然后添加 starTimer 方法,每隔 1s 执行一次。

然后再修改 mainActivity 中方法,把 this 传递进去;

MethodChannelTest(this,flutterEngine.dartExecutor.binaryMessenger)

运行程序后,UI 效果如下:

image.png

相关代码改动可以查看本次 Git 提交记录