插件开发:平台通道简介
平台特定或特定平台指的是Flutter运行的平台,如Android或iOS,即应用的原生部分。平台通道是Flutter与原生平台之间通信的桥梁,也是Flutter插件的底层基础设施。
Flutter使用了一个灵活的系统,允许开发者调用特定平台的API,无论是在Android上的Java或Kotlin代码中,还是iOS上的Objective-C或Swift代码中均可用。
Flutter与原生之间的通信依赖灵活的消息传递方式:
- 应用的Flutter部分通过平台通道将消息发送到其应用程序所在的宿主(iOS或Android)应用,即原生应用。
- 宿主监听平台通道并接收消息,然后调用该平台的API,并将响应发送回客户端,即应用程序的Flutter部分。
平台通道
平台通道在Flutter(客户端)和原生(宿主)之间传递消息。当在Flutter中调用原生方法时,调用信息通过平台通道传递到原生,原生收到调用信息后执行指定操作,如需返回数据,则原生将数据再通过平台通道传递给Flutter。消息传递是异步的,这确保了用户界面在消息传递时不会被挂起。
在客户端,MethodChannel API可以发送与方法调用相对应的消息。在宿主平台上,Android的MethodChannel和iOS的FlutterMethodChannel可以接收方法调用并返回结果。这些类帮助我们用很少的代码开发平台插件。
注意:方法调用可以是反向的,即宿主作为客户端调用Dart中实现的API,例如quick_actions插件。
平台通道数据类型支持
平台通道使用标准消息编解码器对消息进行编解码,高效地进行二进制序列化与反序列化。由于Dart与原生平台之间数据类型存在差异,以下是数据类型之间的映射关系:
| Dart 类型 | Android 类型 | iOS 类型 |
|---|---|---|
| null | null | nil (NSNull when nested) |
| bool | java.lang.Boolean | NSNumber numberWithBool: |
| int | java.lang.Integer | NSNumber numberWithInt: |
| int (如果32位不够) | java.lang.Long | NSNumber numberWithLong: |
| int (如果64位不够) | 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 |
在发送和接收值时,这些值在消息中的序列化和反序列化会自动进行。
开发Flutter插件
使用平台通道调用原生代码,下面通过一个获取电池电量的插件来介绍Flutter插件的开发流程。该插件在Dart中通过getBatteryLevel调用Android BatteryManager API和iOS device.batteryLevel API。
创建一个新的应用程序项目
首先创建一个新的应用程序。在终端中运行:flutter create batterylevel。默认情况下,模板支持使用Java编写Android代码,或使用Objective-C编写iOS代码。要使用Kotlin或Swift,请使用-i和/或-a标志,例如运行:flutter create -i swift -a kotlin batterylevel。
创建Flutter平台客户端
该应用的State类拥有当前的应用状态。我们需要扩展它以保持当前的电量。首先,构建通道,使用MethodChannel调用一个方法来返回电池电量。通道的客户端和宿主通过通道构造函数中传递的通道名称进行连接。单个应用中使用的所有通道名称必须是唯一的;建议在通道名称前加一个唯一的“域名前缀”,例如samples.flutter.io/battery。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.io/battery');
String _batteryLevel = 'Unknown battery level.';
Future<Null> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
@override
Widget build(BuildContext context) {
return new Material(
child: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
new RaisedButton(
child: new Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
new Text(_batteryLevel),
],
),
),
);
}
}
在接下来的部分中,会分别介绍Android和iOS端API的实现。
自定义平台通道和编解码器
除了MethodChannel,还可以使用BasicMessageChannel,它支持使用自定义消息编解码器进行基本的异步消息传递。此外,可以使用专门的BinaryCodec、StringCodec和JSONMessageCodec类,或创建自己的编解码器。