Flutter 与原生交互
Flutter 与原生交互
一.信息传递方面
1.通过dart的Channel进行通信
平台通道的作用是在 Flutter 和设备之间传递消息,如下所示:
Flutter 中平台特定的 API 支持并不依赖于代码生成,而是依赖于灵活的消息传递方式:
应用程序的 Flutter 部分通过平台通道(Platform Channel)将消息发送到该应用程序的设备平台(Android 或 iOS)
设备平台通过对平台通道的监听接收到消息之后,调用设备平台上对应的 API(使用设备平台原生开发语言),然后将响应数据发送回应用程序的 Flutter 部分
1).平台通道类型
Flutter 中定义了三种不同类型的平台通道
-
BasicMessageChannel:用于传递字符串和半结构化的消息
-
MethodChannel 和 OptionalMethodChannel:用于传递方法调用
-
EventChannel:用于传递数据流
上述三种类型的平台通道相互独立,都有着各自的用途。从源码的角度来说,每个平台通道都有三个非常重要的成员变量:
name: String 类型,表示平台通道的名称,也是通道的唯一标识符
BinaryMessenger: BinaryMessenger 类型,用于表示消息,是接收和发送消息的具体实现
codec: MessageCodec 或者 MethodCodec 类型,是消息的编解码器
name
name 属性是平台通道的唯一标识符。一个 Flutter 应用中可能会存在多个平台通道,每一个平台通道在创建时必须指定一个位移的 name 属性(Flutter 使用该属性来区分每一个通道)。
当有消息从 Flutter 端发送到设备平台时,会根据其传递过来的 name 属性找到该通道对应的 Handler(消息处理器)。
BinaryMessenger
上述三种类型的平台通道都是通过 BinaryMessenger 达到与 Flutter 通信的目的的:
BinaryMessenger 使用的消息数据格式为二进制格式的数据。当我们初始化一个通道并向该通道注册消息处理器(Handler)时,实际上会生成一个与之对应的 BinaryMessageHandler,并以通道的唯一标示符(name)作为 key 存储到 Map<String, _MessageHandler> _handlers (定义在 platform_messages.dart/BinaryMessages 中)对象中。当我们需要使用 BinaryMessenger 发送消息时,会根据 name 属性查找对应的 BinaryMessageHandler,然后完成消息的发送。
BinaryMessages 在 Android 端是一个接口,其具体实现是 FlutterNativeView。在 iOS 端是一个协议,其名称为 FlutterBinaryMessenger,FlutterViewController 遵循了它。
由于平台通道从 BinaryMessengerHandler 接收到的消息是二进制格式的,需要使用 codec 将消息解码为可识别的数据并传递给 Handler 进行处理。
当 Handler 处理完消息之后,会通过回调方法返回 result,并将 result 通过 codec 编码为二进制数据,最终通过 BinaryMessenger 发送回 Flutter。
2).MethodChannel,EventChannel,BasicMessageChannel通信示例
MethodChannel 通信示例:
flutter端调用平台端系统电量方法
iOS端调用系统电量实现
Android端调用系统电量实现
EventChannel通信示例:
flutter端接收平台端系统电量方法
iOS端调用系统电量实现
Android端调用系统电量实现
BasicMessageChannel通信示例:
flutter端接收平台端系统电量方法
iOS端发送方法
Android端发送方法
遇到问题:需要dart,iOS,Android共同维护代码,代码重复率高,时间成本高。
iOS端最开始尝试解决方案,通过方法及参数,把字符串动态生成实例方法。比之前的代码强一点,但是还没有达到理想程度。
如何解决只只需在dart,iOS,Android三端维护一份代码,达到通信的需求呢?
二.自定义通道和编解码器
codec
Flutter 中定义了两种类型的 codec:
-
MessageCodec:用于二进制格式的数据与基础数据之间的编解码,有如下四种定义
1).BinaryCodec: 二进制数据的编解码
2).StringCodec: String 类型和二进制数据之间的编解码(UTF-8)
3)JSONMessageCodec: JSON 格式的基础数据类型与二进制数据之间的编解码
4)StandardMessageCodec: BasicMessageChannel 的默认编解码器
-
MethodCodec:用于二进制格式的数据与方法调用以及返回结果之间的编解码,有如下两种类型的定义(message_codecs.dart):
1)JSONMethodCodec:编解码JSON类型的方法调用,实际上依赖于JSONMessageCodec
2)StandardMethodCodec:MethodCodec 的默认实现,二进制类型的方法调用编解码器,实际上依赖于 StandardMessageCodec
如图所示:
StandardMessageCodec
StandardMessageCodec 用于基本数据类型与二进制数据的编解码。
下表显示了 StandardMessageCodec 中需要转换的基础数据类型在 Android 和 iOS 中对应的关系:
示例Flutter源码JSONMessageCodec:
class JSONMessageCodec implements MessageCodec<dynamic> {
// The codec serializes messages as defined by the JSON codec of the
// dart:convert package. The format used must match the Android and
// iOS counterparts.
/// Creates a [MessageCodec] with UTF-8 encoded JSON messages.
const JSONMessageCodec();
@override
ByteData encodeMessage(dynamic message) {
if (message == null)
return null;
return const StringCodec().encodeMessage(json.encode(message));
}
@override
dynamic decodeMessage(ByteData message) {
if (message == null)
return message;
return json.decode(const StringCodec().decodeMessage(message));
}
}
JSONMethodCodec源码
class JSONMethodCodec implements MethodCodec {
// The codec serializes method calls, and result envelopes as outlined below.
// This format must match the Android and iOS counterparts.
//
// * Individual values are serialized as defined by the JSON codec of the
// dart:convert package.
// * Method calls are serialized as two-element maps, with the method name
// keyed by 'method' and the arguments keyed by 'args'.
// * Reply envelopes are serialized as either:
// * one-element lists containing the successful result as its single
// element, or
// * three-element lists containing, in order, an error code String, an
// error message String, and an error details value.
/// Creates a [MethodCodec] with UTF-8 encoded JSON method calls and result
/// envelopes.
const JSONMethodCodec();
@override
ByteData encodeMethodCall(MethodCall call) {
return const JSONMessageCodec().encodeMessage(<String, dynamic>{
'method': call.method,
'args': call.arguments,
});
}
@override
MethodCall decodeMethodCall(ByteData methodCall) {
final dynamic decoded = const JSONMessageCodec().decodeMessage(methodCall);
if (decoded is! Map)
throw FormatException('Expected method call Map, got $decoded');
final dynamic method = decoded['method'];
final dynamic arguments = decoded['args'];
if (method is String)
return MethodCall(method, arguments);
throw FormatException('Invalid method call: $decoded');
}
@override
dynamic decodeEnvelope(ByteData envelope) {
final dynamic decoded = const JSONMessageCodec().decodeMessage(envelope);
if (decoded is! List)
throw FormatException('Expected envelope List, got $decoded');
if (decoded.length == 1)
return decoded[0];
if (decoded.length == 3
&& decoded[0] is String
&& (decoded[1] == null || decoded[1] is String))
throw PlatformException(
code: decoded[0],
message: decoded[1],
details: decoded[2],
);
throw FormatException('Invalid envelope: $decoded');
}
@override
ByteData encodeSuccessEnvelope(dynamic result) {
return const JSONMessageCodec().encodeMessage(<dynamic>[result]);
}
@override
ByteData encodeErrorEnvelope({@required String code, String message, dynamic details}) {
assert(code != null);
return const JSONMessageCodec().encodeMessage(<dynamic>[code, message, details]);
}
}
从源码中可以看出,JSONMethodCodec 实际上是依赖 JSONMessageCodec 实现的。当使用 JSONMethodCodec 编码方法调用时,会先将方法调用转化为 {“method”:method,“args”:args},然后通过 JSONMessageCodec().encodeMessage 完成调用。而 JSONMethodCodec 在编码调用结果 result 时,会先通过 JSONMessageCodec().decodeMessage(envelope) 将其转换为一个数组,如果调用成功,则通过 JSONMessageCodec().encodeMessage([result]) 将 result 编码为二进制数据,如果失败,则通过 JSONMessageCodec().encodeMessage([code, message, details]) 将 code、message 和 details 编码为二进制数据。
三.Channel和 thread
即使Flutter异步向Dart发送消息或从Dart发送消息,无论何时调用channel方法,都必须在平台的主线程上调用该方法
android上成为 UI thread
,
iOS为 main thread
.
四.通过Pigone进行类型安全的消息互通
解决方案: 通过Pigone,只需要维护dart一份代码,通过命令动态生成iOS,安卓代码。类型更加安全,代码更加清晰。
Pigeone是一种代码生成器工具,可以使Flutter与主机平台之间的通信类型安全且更加容易。
https://pub.dev/packages/pigeon/
五.flutter交互问题
iOS这边接入遇到的一些问题以及注意点:
1).FlutterEngine内存泄露问题:
iOS端通过隐式FlutterEngine创建引起内存泄露例如:
let vc = FlutterViewController()
每次进入flutter页面,内存固定增加50M左右,只增不减。
解决办法:通过FlutterEngine
创建FlutterViewController
,保证一个FlutterEngine
对应一个FlutterViewController
,FlutterEngine
保持唯一性,建议使用单利进行创建。
2).iOS不推荐使用setInitialRoute方法初始化参数,通过FlutterEngine
创建FlutterViewController
会导致在flutter
端的window.defaultRouteName
始终为“/”。
解决办法通过FlutterBasicMessageChannel
通道传递消息
3).三方组件注册问题:
如果dart用到了plugin在iOS端和安卓端,需要对进行注册
iOS在appDelegate里面进行注册
Android 需要在MainActivity里面进行注册
还需要把flutter中三方组件打包成framework引入到flutter组件中,以供调用
参考的相关文章
本文地址:https://blog.csdn.net/weixin_37737915/article/details/109994658
下一篇: 数据库之SQL技巧整理案例
推荐阅读
-
Android基础之Fragment与Activity交互详解
-
Activity与Service之间交互并播放歌曲的实现代码
-
Android的WebView与H5前端JS代码交互的实例代码
-
c#两种方式调用google地球,调用COM API以及调用GEPLUGIN 与js交互,加载kml文件,dae文件。将二维高德地图覆盖到到三维谷歌地球表面。
-
ASP.Net WebAPI与Ajax进行跨域数据交互时Cookies数据的传递
-
JS与OC交互,JS中调用OC方法(获取JSContext的方式)
-
Android APP与媒体存储服务的交互
-
详解 WebView 与 JS 交互传值问题
-
Android开发使用json实现服务器与客户端数据的交互功能示例
-
python文件读写操作与linux shell变量命令交互执行的方法