美文网首页Flutter圈子Flutter中文社区Flutter
Flutter 与 Native 通信详解(上):原理探究

Flutter 与 Native 通信详解(上):原理探究

作者: 等开会 | 来源:发表于2019-07-14 21:23 被阅读4次

    Flutter 简介

    Flutter 是 Google 开发的一套全新的跨平台框架,不同于 React Native 封装原生应用层接口,然后通过 JavaScriptCore 转义 JavaScript 来生成原生界面的方案,Flutter 抛开原生控件,用 Dart 语言重写了一套跨平台的UI组件(widget),渲染引擎依靠跨平台的 Skia 图形库来实现,依赖系统的只有图形绘制相关的接口,可以在最大程度上保证不同平台、不同设备的体验一致性,前段时间 Google I/O 2019 大会上宣布 Flutter 还将支持 Web端、桌面端和嵌入式设备,可谓真正实现了 Write once, run anywhere.

    Flutter 和 Native 通信的必要性

    看起来 Flutter 做了很多,似乎完全不需要使用原生平台的功能也能构建一个 App,但是大部分情况下,光靠 Flutter 是不够的,比如当你的App需要如下功能时:

    • 获取设备信息,像电池电量、网络连接状态等
    • 使用相机,麦克风,蓝牙,定位等功能
    • 数据持久化,通知,App 生命周期等

    当然,这些平台(即Native端,下同)相关的功能 Flutter 其实也可以封装在 Flutter 框架中,让开发者不用关心平台,完全通过Flutter 构建App,多好啊;但是这样做会带来一些问题,且不论将Android、iOS(之后还会包括Web端、桌面端、嵌入式设备)各端的平台功能都封装好的难易度,就说如果都封装到 Flutter 中了,那么Flutter framework 变得比现在大很多,且只要平台相关API有所更新,Flutter 也得跟着修改,会出现 Flutter 一直在追逐平台版本的情况,也容易出现兼容性问题和版本碎片化.

    所以,Flutter 团队并没有选择封装平台相关的API,选择了另一种更灵活的做法:Flutter 依旧用 Dart 及跨平台的渲染引擎来实现 Write once, run anywhere的界面和业务逻辑,在涉及到平台相关的功能时,则还是由开发者在平台端实现,然后提供了一个叫做 Platform Channel 的机制来进行 Flutter 和平台端之间的通信,这样做可以将Flutter和平台的耦合度降到最低。

    image
    Platform Channel 见名知意,即平台通道,是Flutter和原生平台通信的通道,分为以下三类:
    • Message channel:用于传递字符串和半结构化的信息。
    • Method channel:用于传递方法调用(method invocation)。
    • Event channel:用于数据流(event streams)的通信。

    本篇文章先不着急介绍这三类 Platform channel,先来看看 Flutter 和平台端通信的原理。

    Flutter 和 Native 通信的原理

    消息信使:BinaryMessenger

    从底层来看,Flutter和平台端通信的方式是发送异步的二进制消息,该基础通信方式在Flutter端由BinaryMessages来实现,​ 而在Android端是一个接口BinaryMessenger,其具体实现为FlutterNativeView,在iOS端是一个协议 FlutterBinaryMessengerFlutterViewController遵守并实现了这个协议。

    image
    其主要实现了发送二进制消息和设置消息处理回调的方法,如Flutter端BinaryMessages 的部分源码:
        // 发送二进制消息
      static Future<ByteData> send(String channel, ByteData message) {
        final _MessageHandler handler = _mockHandlers[channel];
        if (handler != null)
          return handler(message);
        return _sendPlatformMessage(channel, message);
      }
    
      // 注册消息处理回调
      static void setMessageHandler(String channel, Future<ByteData> handler(ByteData message)) {
        if (handler == null)
          _handlers.remove(channel);
        else
          _handlers[channel] = handler;
      }
    

    消息通道:Channel

    同时,为了区分不同用途的消息,每个消息都可以为其指定一个channel,即以上消息收发方法中的参数 channel,channel 仅仅是一个字符串,下面的例子使用foo当做消息收发的channel:

    //向平台发送二进制消息.
    final WriteBuffer buffer = WriteBuffer()
      ..putFloat64(3.1415)
      ..putInt32(12345678);
    final ByteData message = buffer.done();
    await BinaryMessages.send('foo', message);
    print('Message sent, reply ignored');
    

    Android 端(Kotlin):

    // 接受并解析来自Flutter端的消息
    flutterView.setMessageHandler("foo") { message, reply ->
      message.order(ByteOrder.nativeOrder())
      val x = message.double
      val n = message.int
      Log.i("MSG", "Received: $x and $n")
      reply.reply(null)
    }
    

    iOS端(OC):

    // 接受并解析来自Flutter端的消息
    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
    [controller setMessageHandlerOnChannel:@"foo" binaryMessageHandler:^(NSData * _Nullable message, FlutterBinaryReply  _Nonnull reply) {
        Float64 x;
        NSData *data8 = [message subdataWithRange:NSMakeRange(0, 8)];
        [data8 getBytes:&x length:sizeof(x)];
        
        int32_t n;
        NSData *data4 = [message subdataWithRange:NSMakeRange(8, 4)];
        [data4 getBytes:&n length:sizeof(n)];
        
        NSLog(@"Received %f and %d", x, n);
        reply(nil);
    }];
    

    消息通信是双向的,所以我们也可以从平台端向Flutter端发送消息,如下:

    // 从 Android 端发送一个二进制消息
    val message = ByteBuffer.allocateDirect(12)
    message.putDouble(3.1415)
    message.putInt(123456789)
    flutterView.send("foo", message) { _ ->
      Log.i("MSG", "Message sent, reply ignored")
    }
    
    // 从 iOS端发送一个二进制消息
    NSMutableData *message = [NSMutableData dataWithCapacity:12];
    Float64 x = 3.1415;
    int32_t n = 12345678;
    [message appendBytes:&x length:sizeof(x)];
    [message appendBytes:&n length:sizeof(n)];
    [controller sendOnChannel:@"foo" message:message binaryReply:^(NSData * _Nullable reply) {
        NSLog(@"Message sent, reply ignored");
    }];
    
    // Flutter端接收
    BinaryMessages.setMessageHandler('foo', (ByteData message) async {
      final ReadBuffer readBuffer = ReadBuffer(message);
      final double x = readBuffer.getFloat64();
      final int n = readBuffer.getInt32();
      print('Received $x and $n');
      return null;
    });
    

    消息处理器:MessageHandler

    通过上面的示例代码可以发现,消息是通过提前设置的MessageHandler来处理的,所有的 MessageHandler 都被保存在一个 HashMap 中,key 即为其对应的 channel 字符串,因此每个channel最多只能有一个 MessageHandler,后设置的会将之前的覆盖掉,取消一个 MessageHandler 的方式也就是设置对应 channel 的 MessageHandler 为 null.

    在 MessageHandler 中最后的消息回复动作是必须的,每个消息的发送都应该对应一个异步的消息回复,即使没有返回值,也需要回复 null,就像示例代码中一样,这是为了使得Dart中的Future和平台端的回调函数得以完成和执行。

    还有一点需要注意的是,在平台端消息的发送和回复都必须在主线程进行(即UI线程),而在flutter端,每个 Dart isolate 只有一个线程,所以flutter端不用担心用错线程所导致的问题。

    关于Dart isolate详细讲解可以参考闲鱼团体的文章:Flutter Engine线程管理与Dart Isolate机制

    消息编解码器:Codec

    在上面的例子中,我们其实已经能够在Flutter和平台间进行相互通信了,但是收发的数据都是二进制的,这就需要开发者考虑更多的细节,如字节顺序(大小端)和怎么表示更高级的消息类型,如字符串,map等,因此,Flutter 还提供了消息编解码器(Codec), 用于高级数据类型(字符串,map等)和二进制数据(byte)之间的转换,即消息的序列化和反序列化。

    有了消息编解码器,我们在编程时就不用直接对二进制数据进行操作了,极大的降低了编程复杂度,Flutter 定义了四种基本的消息编解码器类型:

    • BinaryCodec:BinaryCodec是最为简单的一种Codec,因为其返回值类型和入参的类型相同,均为二进制格式(Android中为ByteBuffer,iOS中为NSData)。实际上,BinaryCodec在编解码过程中什么都没做,只是原封不动将二进制数据消息返回而已。或许你会因此觉得BinaryCodec没有意义,但是在某些情况下它非常有用,比如使用BinaryCodec可以使传递内存数据块时在编解码阶段免于内存拷贝。

    • StringCodec:使用 UTF-8 编码格式对字符串数据进行编解码,在Android平台转换为 java.util.String 类型,iOS 平台则对应着 NSString.

    • JSONMessageCodec:JSONMessageCodec用于处理 JSON 数据类型(字符串型,数字型,布尔型,null,只包含这些类型的数组,和key为string类型,value为这些类型的map),在编码过程中,数据会被转换为JSON字符串,然后在使用 UTF-8 格式转换为字节型。其在iOS端使用了NSJSONSerialization作为序列化的工具,而在Android端则使用了其自定义的JSONUtil与StringCodec作为序列化工具。

    • StandardMessageCodec:StandardMessageCodec 可以认为是 JSONMessageCodec 的升级版,能够处理的数据类型要比 JSONMessageCodec 更普遍一些,且在处理 int 型数据时,会根据 int 数据的大小来转为平台端的32位类型(int)或者是64位类型(long),StandardMessageCodec 也是 Flutter Platform channel 的默认编解码器,下图列出了 StandardMessageCodec 能处理的数据类型和在各平台对应的类型:

    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:
    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

    需要注意的是 BigInteger 类型在 Dart 2.0 已被废弃,这是因为在Dart 1.0 时,int 类型没有固定的大小限制,32位、64位数字都可以用 int 表示,而当Dart中的int型传到平台端时,就会根据其具体大小转为 int型、long型或者更大的类型,BigInteger 就是用来标识比64位int更大的类型的;但是到了Dart 2.0 ,int 类型大小固定为了 64位,如果想要传递更大的数字,则需要转换为字符串类型。

    再深入一点,通过查看 flutter engine源码 可以发现,当message或response需要被编码为二进制数据时,会调用StandardMessageCodec 的 writeValue方法,该方法接收一个名为value的参数,并根据其类型,向二进制数据容器(NSMutableData或ByteArrayOutputStream)写入该类型对应的type值,再将该数据转化为二进制表示,并写入二进制数据容器。

    ​而message或者response需要被解码时,使用的是StandardMessageCodec的readValue方法,该方法接收到二进制格式数据后,会先读取一个byte表示其type,再根据其type将二进制数据转化为对应的数据类型。

    假设我们要发送的消息为int型的数字 100,当这个值被转化为二进制数据时,会先向二进制数据容器写入int类型对应的type值:3,再写入由100转化而得的4个byte。而当Flutter端接收到该二进制数据时,先读取第一个byte值,并根据其值得出该数据为int类型,接着读取紧跟其后的4个byte,并将其转化为dart类型的int,反之亦然。


    image

    对于字符串、列表、字典的编码会稍微复杂一些。字符串使用UTF-8编码得到的二进制数据是长度不定的,因此会在写入type后,先写入一个代表二进制数据长度的size,再写入数据。列表和字典则是写入type后,先写入一个代表列表或字典中元素个数的size,再递归调用writeValue方法将其元素依次写入。

    至于消息编解码器的具体用法,就要说到 Platform channel 了,其实就是对于以上介绍的几个通信基础要素的组合封装,这里由于篇幅问题,下篇文章再说。

    总结

    到这里,想必你已经理解了flutter和平台端通信的原理:通过消息信使(BinaryMessenger)来异步的收发二进制消息,每个消息都有对应的消息渠道(channel)来区分不同的消息用途,然后使用不同的消息编解码器(Codec)对二进制数据进行序列化与反序列化,最后通过注册的消息处理器(MessageHandler)来处理并回复对应的消息。


    image

    参考

    Flutter Platform Channels
    深入理解Flutter Platform Channel
    flutter engine 源码
    Writing custom platform-specific code

    相关文章

      网友评论

        本文标题:Flutter 与 Native 通信详解(上):原理探究

        本文链接:https://www.haomeiwen.com/subject/ttoikctx.html