微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

一张图理解Flutter中Dart与原生环境通信

一张图理解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] 举报,一经查实,本站将立刻删除。

相关推荐