一张图理解Flutter中Dart与原生环境通信
Flutter中提供了Dart与原生环境通信的机制Platform Channels。通过该机制可以扩展Flutter,实现调用原生系统Api的能力。官方介绍图如下:
那么Flutter是怎么实现这套机制的?在设计这套机制时有哪些值得关注的重点?
这里以Android为例,以一张图解释整个通信流程。
废话不多说,先上图:
其中Java与Dart两个语言环境通过C++层做消息中转。Java与C++通信的技术叫做JNI,Dart与C++的通信与JNI类似,可以叫做NativeBinding。
在设计整个机制时,需要注意一下几点。
编码与解码
由于不同语言中的数据类型是不同的,所以在数据传递过程中,需要将其转换成大家都能理解的数据类型。Flutter中支持的数据类型对应关系如下:
Dart | Android | iOS |
---|---|---|
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
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 |
Android环境的编解码是由MessageCodec接口定义的:
package io.flutter.plugin.common;
public interface MessageCodec<T> {
@Nullable
ByteBuffer encodeMessage(@Nullable T var1);
@Nullable
T decodeMessage(@Nullable ByteBuffer var1);
}
为了实现不同语言的数据类型对应与转换,需要寻找一种大家都认识的数据类型,也就是计算机世界的最基本的数据类型—byte字节。
所以Flutter中的标准编解码器,将不同的数据类型高效的编码成字节序列。下面以编解码一个int类型为例做说明:
int编码
//字节数组输出流
ByteArrayOutputStream stream;
//Int类型标识
private static final byte INT = 3;
//先写入int类型标识,表明接下来的四个字节要组成一个int整型。
stream.write(3);
protected static final void writeInt(ByteArrayOutputStream stream, int value) {
//依次写入int低8位
//这次要区分系统平台的字节序,分为大端序和小端序
if (LITTLE_ENDIAN) {
stream.write(value);
stream.write(value >>> 8);
stream.write(value >>> 16);
stream.write(value >>> 24);
} else {
stream.write(value >>> 24);
stream.write(value >>> 16);
stream.write(value >>> 8);
stream.write(value);
}
}
int解码
protected final Object readValue(ByteBuffer buffer) {
if (!buffer.hasRemaining()) {
throw new IllegalArgumentException("Message corrupted");
} else {
//先读取一个字节,判断是什么类型的数据
byte type = buffer.get();
return this.readValueOfType(type, buffer);
}
}
protected Object readValueOfType(byte type, ByteBuffer buffer) {
//如果是int整型数据,则一次性读取四个字节,然后返回一个int整型
case 3:
result = buffer.getInt();
break;
}
通过上面的编码,就可以将Java层中的数据传递给C++层,C++层转发给Dart后,Dart层在按照相同的规则解码成Dart语言中对应的数据类型。
除了标准的编解码方式StandardMethodCodec,Flutter还提供了其他三种方式:
- BinaryCodec
- StringCodec
- JSONMessageCodec
BinaryCodec 用于直接传输字节数组,没有做任何操作
StandardMethodCodec 用于PlatformViewsChannel
StringCodec用于字符串和字节数组的转换,例如Flutter中生命周期渠道LifecycleChannel、
JSONMessageCodec用于大部分场景,如键盘事件KeyEventChannel、页面导航事件NavigationChannel、平台事件PlatformChannel、文本编辑事件TextInputChannel等
返回值回调
从发送端到目的端的一次通信过程很简单,但是如果发送端需要获得目的端的响应结果,类似于一个有返回值的方法调用,那么这个返回值如何正确的响应给发送端?
下面看一下Flutter中的做法:
package io.flutter.embedding.engine.dart;
class DartMessenger implements BinaryMessenger, PlatformMessageHandler {
private final Map<Integer, BinaryReply> pendingReplies;
private int nextReplyId = 1;
// 当发送消息时,可以指定BinaryReply类型的回调
public void send(@NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryReply callback) {
Log.v("DartMessenger", "Sending message with callback over channel '" + channel + "'");
// 为callback生成唯一标识,存储到Map容器pendingReplies中
int replyId = 0;
if (callback != null) {
replyId = this.nextReplyId++;
this.pendingReplies.put(replyId, callback);
}
if (message == null) {
this.flutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
} else {
this.flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId);
}
}
//消息处理完成后,根据回调标识replyId从Map容器pendingReplies中取出对应回调BinaryReply实例并执行
public void handlePlatformMessageResponse(int replyId, @Nullable byte[] reply) {
Log.v("DartMessenger", "Received message reply from Dart.");
BinaryReply callback = (BinaryReply)this.pendingReplies.remove(replyId);
if (callback != null) {
try {
Log.v("DartMessenger", "Invoking registered callback for reply from Dart.");
callback.reply(reply == null ? null : ByteBuffer.wrap(reply));
} catch (Exception var5) {
Log.e("DartMessenger", "Uncaught exception in binary message reply handler", var5);
}
}
}
}
通过上面源码分析可以知道,当需要返回值时需要传出一个回调callback实例,但是这个callback实例不会随着消息传递给Dart层,而是生成一个整型标识与之对应。通过传递这个整型标识来决定由哪个回调来处理返回值。
区分不同的应用场景
通过前面的编解码部分,我们了解了Flutter中提供了四种编解码方式,这四种编解码方式提供了对不同数据类型处理的能力,包含基础数据类型、String、JSON、字节数组,他们应用在了不同的通信渠道场景中。
那么所有的通信渠道场景都是基于三种基础通信渠道衍生出来的。
- BasicMessageChannel
- EventChannel
- MethodChannel
这三种通信模式不关心具体编解码方式,而是依据特定的使用场景添加了额外的操作功能。
BasicMessageChannel
用于使用基本的异步消息传递与Flutter应用程序通信的命名通道。
@UiThread
public void send(@Nullable T message, @Nullable BasicMessageChannel.Reply<T> callback) {
this.messenger.send(this.name, this.codec.encodeMessage(message), callback == null ? null : new BasicMessageChannel.IncomingReplyHandler(callback));
}
这种通信方式实现了最基本的信息发送和接收,只是添加了一步编解码操作。
EventChannel
用于和平台插件以事件流方式通信的命名通道。例如监听手机电量、GPS位置等,需要原生系统不停的向Flutter应用发送变化后的数据。
既然是事件流,就必须提供连个操作接口:
添加监听
取消监听
如下代码所示:
public interface StreamHandler {
void onListen(Object var1, EventChannel.EventSink var2);
void onCancel(Object var1);
}
当Dart层需要对某个EventChannel的事件流发起监听时,需要调用listen方法,对应到StreamHandler的onListen。当有新的事件发生时,通过EventSink将消息封装好后,传递给Dart层的事件接口函数_onEvent,该函数名是固定的。
当Dart层不再需要接受新的事件时,需要调用cancel方法,对应StreamHandler的onCancel。之后原生平台就不会发送新事件,并且释放本地的资源。
MethodChannel
用于和平台插件进行异步方法调用通信的命名管道。
@UiThread
public void invokeMethod(String method, @Nullable Object arguments, MethodChannel.Result callback) {
this.messenger.send(this.name,
this.codec.encodeMethodCall(new MethodCall(method, arguments)),
callback == null ? null : new MethodChannel.IncomingResultHandler(callback));
}
MethodChannel对外提供了invokeMethod方法,可以指定方法名称、参数、返回值回调,整个数据流转流程和我们在一开始给出的图中是一样的。
其内部实现就是把所有东西封装成一个消息。
总结
通过对Flutter中PlatformChannel源码的梳理可以知道,Flutter中跨语言通信的原理就是将需要传递的内容编码成字节数组通过C++层传递到Dart层。各个层对应的数据类型分别是ByteBuffer(Java) -> uint8_t*(C++) -> ByteData(Dart)。
同时,为了方便开发者使用,还针对不同的使用场景做了封装,如方法调用MethodChannel、事件流EventChannel。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。