Flutter框架:深入理解Flutter Platform Channel
作者:闲鱼技术-皓黯 相信读者们在阅读了我们之前的文章后,对Platform Channel有了一定的理解和认识。但是由于篇幅有限,上文并未对Platform Channel的工作原理进行详细的讲解。Platform Channel如何工作,消息如何从Flutter端传递到Platform端,消息如何编解码,Platform Channel工作在什么线程上,是否线程安全,Platform Channel能否传递大内存数据块?本文试图结合官方例子,对上述问题进行详细的讲解。 1. 理解Platform Channel工作原理Flutter定义了三种不同类型的Channel,它们分别是
三种Channel之间互相独立,各有用途,但它们在设计上却非常相近。每种Channel均有三个重要成员变量:
1.1. Channel name一个Flutter应用中可能存在多个Channel,每个Channel在创建时必须指定一个独一无二的name,Channel之间使用name来区分彼此。当有消息从Flutter端发送到Platform端时,会根据其传递过来的channel name找到该Channel对应的Handler(消息处理器)。 1.2. 消息信使:BinaryMessengerbinaryMessager 虽然三种Channel各有用途,但是他们与Flutter通信的工具却是相同的,均为BinaryMessager。 BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。 Binarymessenger在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。 Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。 当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端。 1.3. 消息编解码器:CodecbinaryMessager 消息编解码器Codec主要用于将二进制格式的数据转化为Handler能够识别的数据,Flutter定义了两种Codec:MessageCodec和MethodCodec。 1.3.1. MessageCodecMessageCodec用于二进制格式数据与基础数据之间的编解码。BasicMessageChannel所使用的编解码器就是MessageCodec。 Android中,MessageCodec是一个接口,定义了两个方法: MessageCodec有多种不同的实现:
1.3.2. MethodCodecMethodCodec用于二进制数据与方法调用(MethodCall)和返回结果之间的编解码。MethodChannel和EventChannel所使用的编解码器均为MethodCodec。 与MessageCodec不同的是,MethodCodec用于MethodCall对象的编解码,一个MethodCall对象代表一次从Flutter端发起的方法调用。MethodCall有2个成员变量:String类型的 由于处理的是方法调用,故相比于MessageCodec,MethodCodec多了对调用结果的处理。当方法调用成功时,使用 MethodCodec有两种实现:
1.4. 消息处理器:Handler当我们接收二进制格式消息并使用Codec将其解码为Handler能处理的消息后,就该Handler上场了。Flutter定义了三种类型的Handler,与Channel类型一一对应。我们向Channel注册一个Handler时,实际上就是向BinaryMessager注册一个与之对应的BinaryMessageHandler。当消息派分到BinaryMessageHandler后,Channel会通过Codec将消息解码,并传递给Handler处理。 1.4.1. MessageHandlerMessageHandler用户处理字符串或者半结构化的消息,其 1.4.2. MethodHandlerMethodHandler用于处理方法的调用,其 1.4.3. StreamHandlerbinaryMessager StreamHandler与前两者稍显不同,用于事件流的通信,最为常见的用途就是Platform端向Flutter端发送事件消息。当我们实现一个StreamHandler时,需要实现其 实际上,StreamHandler工作原理并不复杂。当我们注册了一个StreamHandler后,实际上会注册一个对应的BinaryMessageHandler到BinaryMessager。而当Flutter端开始监听事件时,会发送一个二进制消息到Platform端。Platform端用MethodCodec将该消息解码为MethodCall,如果MethodCall的method的值为"listen",则调用StreamHandler的 2. 理解消息编解码过程在官方文档《Writing custom platform-specific code with platform channels》中的获取设备电量的例子中我们发现,Android端的返回值是 Flutter官方文档表示, 所以在上文提到的例子中, Flutter默认的消息编解码器是StandardMessageCodec,其支持的数据类型如下: binaryMessager 当message或response需要被编码为二进制数据时,会调用StandardMessageCodec的 而message或者response需要被解码时,使用的是StandardMessageCodec的readValue方法,该方法接收到二进制格式数据后,会先读取一个byte表示其type,再根据其type将二进制数据转化为对应的数据类型。 在获取设备电量的例子中,假设设备的电量为100,当这个值被转化为二进制数据时,会先向二进制数据容器写入int类型对应的type值:3,再写入由电量值100转化而得的4个byte。而当Flutter端接收到该二进制数据时,先读取第一个byte值,并根据其值得出该数据为int类型,接着,读取紧跟其后的4个byte,并将其转化为dart类型的int。 binaryMessager 对于字符串、列表、字典的编码会稍微复杂一些。字符串使用UTF-8编码得到的二进制数据是长度不定的,因此会在写入type后,先写入一个代表二进制数据长度的size,再写入数据。列表和字典则是写入type后,先写入一个代表列表或字典中元素个数的size,再递归调用 3. 理解消息传递过程消息是如何从Flutter端传递到Platform端的呢?接下来我们以一次MethodChannel的调用为例,去理解消息的传递过程。 3.1. 消息传递:从Flutter到Platform3.1.1. Dart层当我们在Flutter端使用MethodChannel的 上述过程最终会调用到ui.Window的
3.1.2. Native层到native层后,window.cc的SendPlatformMessage方法接受了来自dart层的三个参数,并对它们做了一定的处理:dart层的回调
RuntimeDelegate的实现为Engine,Engine在处理Message时,会判断该消息是否是为了获取资源(channel等于"flutter/assets"),如果是,则走获取资源逻辑,否则调用Engine::Delegate的 Engine::Delegate的具体实现为Shell,其 binaryMessager 3.2. 消息处理PlatformView的 3.2.1. PlatformViewAndroidPlatformViewAndroid的是Platformview的子类,也是其在Android端的具体实现。当PlatformViewAndroid接收到PlatformMessage类型的消息时,如果消息中有 Java层中,被调用的代码为FlutterNativeView (BinaryMessager的具体实现)的 BinaryMessageHandler处理完成后,FlutterNativeView会通过JNI调用native的方法,将 native层,PlatformViewAndroid的 binaryMessager 3.2.2. PlatformViewIOSPlatformViewIOS是PlatformView的子类,也是其在iOS端的具体实现,当PlatformViewIOS接收到message时会交给PlatformMessageRouter处理。 PlatformMessageRouter通过PlatformMessage中的 3.3. 结果回传:从Platform到FlutterPlatformMessageResponseDart的 Dart层接收到二进制数据后,使用MethodCodec将数据解码,并返回给业务层。至此,一次从Flutter发起的方法调用就完整结束了。 4. 问题解析4.1. Platform Channel的代码运行在什么线程在文章《深入理解Flutter引擎线程模型》中提及,Flutter Engine自己不创建线程,其线程的创建于管理是由enbedder提供的,并且Flutter Engine要求Embedder提供四个Task Runner,分别是Platform Task Runner,UI Task Runner,GPU Task Runner和IO Task Runner。 实际上,在Platform侧执行的代码运行在Platform Task Runner中,而在Flutter app侧的代码则运行在UI Task Runner中。在Android和iOS平台上,Platform Task Runner跑在主线程上。因此,不应该在Platform端的Handler中处理耗时操作。 4.2. Platform Channel是否线程安全Platform Channel并非是线程安全的,这一点在官方的文档也有提及。Flutter Engine中多个组件是非线程安全的,故跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread。故我们在将Platform端的消息处理结果回传到Flutter端时,需要确保回调函数是在Platform Thread(也就是Android和iOS的主线程)中执行的。 4.3. 是否支持大内存数据块的传递Platform Channel实际上是支持大内存数据块的传递,当需要传递大内存数据块时,需要使用BasicMessageChannel以及BinaryCodec。而整个数据传递的过程中,唯一可能出现数据拷贝的位置为native二进制数据转化为Dart语言二进制数据。若二进制数据大于阈值时(目前阈值为1000byte)则不会拷贝数据,直接转化,否则拷贝一份再转化。 4.4. 如何将Platform Channel原理应用到开发工作中实际上Platform Channel的应用场景非常多,我们这里举一个例子: 在平常的业务开发中,我们需要使用到一些本地图片资源,但是Flutter端是无法使用Platform端已存在的图片资源的。当Flutter端需要使用一个Platform端已有的图片资源时,只有将该图片资源拷贝一份到Flutter的Assert目录下才能使用。实际上,让Flutter端使用Platform端的资源并不是一件难事。 我们可以使用BasicMessageChannel来完成这个工作。Flutter端将图片资源名name传递给Platform端,Native端使用Platform端接收到name后,根据name定位到图片资源,并将该图片资源以二进制数据格式,通过BasicMessageChannel,传递回Flutter端。 总结在Flutter与Native混合开发的模式下,Platform Channel的应用场景非常多,理解Platform Channel的工作原理,有助于我们在从事这方面开发时能做到得心应手。 最后,闲鱼技术团队广招各类方向的达人,无论你是精通移动端,前端,后台,还是机器学习,音视频,自动化测试等,都欢迎投递简历加入我们,一同用技术改善生活! 简历投递:guicai.gxy@alibaba-inc.com 参考https://flutter.io/platform-channels/ https://github.com/flutter/flutter https://github.com/flutter/engine
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |