开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第 7 天,点击查看活动详情
一、前言
Flutter 使用了一个灵活的系统,允许您调用特定平台的 API,无论在 Android 上的 Java 或 Kotlin 代码中,还是 iOS 上的 ObjectiveC 或 Swift 代码中均可用。
Flutter 平台特定的 API 支持不依赖于代码生成,而是依赖于灵活的消息传递的方式:
- 应用的
Flutter部分通过平台通道(platform channel)将消息发送到其应用程序的所在的宿主(iOS或Android)。 - 宿主监听的平台通道,并接收该消息。然后它会调用特定于该平台的
API(使用原生编程语言) - 并将响应发送回客户端,即应用程序的Flutter部分。
二、Platform Channel 的 3 中方式
作为一个UI框架,Flutter 提供了三种通道来和原生平台通信。
- BasicMessageChannel:它提供类似于 BinaryMessages 的基本消息传递服务,但可自定义消息编解码器,支持发送字符串或半结构化消息。用于使用指定的编解码器对消息进行编码和解码,属于双向通信,可以 Native 端主动调用,也可以 Flutter 主动调用
- MethodChannel:它使用异步方法调用的方式进行平台通信。Flutter 与 Native 端相互调用,调用后可以返回结果,可以 Native 端主动调用,也可以 Flutter 主动调用,属于双向通信。此方式为最常用的方式, Native 端调用需要在主线程中执行。
- EventChannel:它使用事件流的方式进行平台通信。Native 端主动发送数据给 Flutter,通常用于状态的监听,比如网络变化、传感器数据等。
平台通道在客户端和宿主之间传递消息,如图所示:
平台通道数据类型支持和解码器:
| Dart | Android | iOS |
|---|---|---|
| null | null | nil (NSNull when nested) |
| bool | java.lang.Boolean | int |
| int | java.lang.Integer | NSNumber numberWithInt |
| int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
| int, if 64 bits not enough | java.math.BigInteger | FlutterStandardBigInteger |
| 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 端通过调用 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-channel 是 MethodChannel 的名称, 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)
}
}
当我们运行起来,点击按钮时,会看到如下效果:
相关代码改动可以查看本次 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 效果如下:
相关代码改动可以查看本次 Git 提交记录。