大佬教程收集整理的这篇文章主要介绍了Flutter框架:深入理解Flutter Platform Channel,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
@H_
489_0@作者:闲鱼技术-皓黯@H_
489_0@相信读者们在阅读了我们之前的文章后,对Platform Ch
Annel有了一定的理解和认识。但是由于篇幅有限,上文并未对Platform Ch
Annel的工作原理进行详细的讲解。Platform Ch
Annel如何工作,消息如何从Flutter端传递到Platform端,消息如何编解码,Platform Ch
Annel工作在什么线程上,是否线程安全,Platform Ch
Annel能否传递大内存数据块?本文试图结合官方例子,对上述问题进行详细的讲解。
1. 理解Platform ChAnnel工作原理
@H_
489_0@Flutter定义了三种不同类型的Ch
Annel,它们分别是
- @H_489_0@BasicmessageChAnnel:用于传递字符串和半结构化的信息。
- @H_489_0@methodChAnnel:用于传递方法调用(method invocation)。
- @H_489_0@EventChAnnel: 用于数据流(event streams)的通信。
@H_
489_0@三种Ch
Annel之间互相独立,各有用途,但它们
在设计上却非常相近。每种Ch
Annel均有三个重要成员变量:
- @H_489_0@name: String类型,代表ChAnnel的名字,也是其唯一标识符。
- @H_489_0@messager:Binarymessenger类型,代表消息信使,是消息的发送与接收的工具。
- @H_489_0@codec: messageCodec类型或MethodCodec类型,代表消息的编解码器。
1.1. ChAnnel name
@H_
489_0@一个Flutter应用中可能存在多个Ch
Annel,每个Ch
Annel在创建时必须指定一个独一无二的name,Ch
Annel之间
使用Name来区分彼此。当有消息从Flutter端发送到Platform端时,会根据其传递过来的ch
Annel name找到该Ch
Annel对应的Handler(消息处理器)。
1.2. 消息信使:Binarymessenger
@H_
489_0@
@H_
489_0@binary
messager@H_
489_0@
虽然三种Ch
Annel各有用途,但是他们与Flutter通信的工具却是相同的,均为Binary
messager。@H_
489_0@Binar
ymessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Ch
Annel,并向该Ch
Annel注册处理消息的Handler时,实际上会生成一个与之对应的Binary
messageHandler,并以ch
Annel name为key,注册到Binar
ymessenger中。当Flutter端发送消息到Binar
ymessenger时,Binar
ymessenger会根据其入参ch
Annel找到对应的Binary
messageHandler,并交由其处理。@H_
489_0@Binar
ymessenger
在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinar
ymessenger,FlutterViewController遵循了它。@H_
489_0@Binar
ymessenger并不知道Ch
Annel的存在,它只和Binary
messageHandler打交道。而Ch
Annel和Binary
messageHandler则是一一对应的。由于Ch
Annel从Binary
messageHandler接收到的消息是二进制格式数据,无法直接使用,故Ch
Annel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。@H_
489_0@当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过Binar
ymessenger发送回Flutter端。
1.3. 消息编解码器:Codec
@H_
489_0@
@H_
489_0@binary
messager@H_
489_0@消息编解码器Codec主要用于将二进制格式的数据转化为Handler能够识别的数据,Flutter定义了两种Codec:
messageCodec和MethodCodec。
1.3.1. messageCodec
@H_
489_0@
messageCodec用于二进制格式数据与基础数据之间的编解码。Basic
messageCh
Annel所使用的编解码器就是
messageCodec。@H_
489_0@Android中,
messageCodec是一个接口,定义了两个方法:
encodemessage
接收一个特定的数据类型T,并将其编码为二进制数据ByteBuffer,而
decodemessage
则接收二进制数据ByteBuffer,将其解码为特定数据类型T。iOS中,其名称为Flutter
messageCodec,是一个协议,定义了两个方法:
encode
接收一个类型为id的消息,将其编码为NSData类型,而
decode
接收NSData类型消息,将其解码为id类型数据。@H_
489_0@
messageCodec有多种不同的实现:
BinaryCodec
@H_489_0@BinaryCodec是最为简单的一种Codec,因为其返回值类型和入参的类型相同,均为二进制格式(Android中为ByteBuffer,iOS中为NSData)。实际上,BinaryCodec在编解码过程中什么都没做,只是原封不动将二进制数据消息返回而已。或许你会因此觉得BinaryCodec没有意义,但是在某些情况下它非常有用,比如使用BinaryCodec可以使传递内存数据块时在编解码阶段免于内存拷贝。
- @H_489_0@StringCodec用于字符串与二进制数据之间的编解码,其编码格式为UTF-8。
JSONmessageCodec
@H_489_0@JSONmessageCodec用于基础数据与二进制数据之间的编解码,其支持基础数据类型以及列表、字典。其在iOS端使用了NSJSONserialization作为序列化的工具,而在Android端则使用了其自定义的JSONUtil与StringCodec作为序列化工具。StandardmessageCodec
@H_489_0@StandardmessageCodec是BasicmessageChAnnel的默认编解码器,其支持基础数据类型、二进制数据、列表、字典,其工作原理会在下文中详细介绍。
1.3.2. MethodCodec
@H_
489_0
@methodCodec用于二进制数据与方法调用(MethodCall)和返回结果之间的编解码。MethodCh
Annel和EventCh
Annel所使用的编解码器均为MethodCodec。@H_
489_0@与
messageCodec不同的是,MethodCodec用于MethodCall对象的编解码,一个MethodCall对象代表一次从Flutter端发起的方法调用。MethodCall有2个成员变量:
String类型的@H_487_73
@method代表需要调用的方法名称,通用类型(Android中为Ob
ject,iOS中为id)的
arguments
代表需要调用的方法入参。@H_
489_0@由于处理的是方法调用,故相比于
messageCodec,MethodCodec多了对调用结果的处理。当方法调用成功时,使用
encodesuccessEnvelope
将result编码为二进制数据,而当方法调用失败时,则使用
encodeErrorEnvelope
将error的code、
message、detail编码为二进制数据。@H_
489_0
@methodCodec有两种实现:
JSONMethodCodec
@H_489_0@JSONMethodCodec的编解码依赖于JSONmessageCodec,当其在编码MethodCall时,会先将MethodCall转化为字典{"method":method,"args":args}
。其在编码调用结果时,会将其转化为一个数组,调用成功为[result]
,调用失败为[code,message,detail]
。再使用JSONmessageCodec将字典或数组转化为二进制数据。StandardMethodCodec
@H_489_0@methodCodec的默认实现,StandardMethodCodec的编解码依赖于StandardmessageCodec,当其编码MethodCall时,会将method和args依次使用StandardmessageCodec编码,写入二进制数据容器。其在编码方法的调用结果时,若调用成功,会先向二进制数据容器写入数值0(代表调用成功),再写入StandardmessageCodec编码后的result。而调用失败,则先向容器写入数据1(代表调用失败),再依次写入StandardmessageCodec编码后的code,message和detail。
1.4. 消息处理器:Handler
@H_
489_0@当我们接收二进制格式消息并
使用COdec将其解码为Handler能处理的消息后,就该Handler上场了。Flutter定义了三种类型的Handler,与Ch
Annel类型一一对应。我们向Ch
Annel注册一个Handler时,实际上就是向Binary
messager注册一个与之对应的Binary
messageHandler。当消息派分到Binary
messageHandler后,Ch
Annel会通过Codec将消息解码,并传递给Handler处理。
1.4.1. messageHandler
@H_
489_0@
messageHandler用户处理字符串或者半结构化的消息,其
onmessage
方法接收一个T类型的消息,并异步返回一个相同类型result。
messageHandler的功能比较基础,使用场景较少,但是其配合BinaryCodec使用时,能够方
便传递二进制数据消息。
1.4.2. MethodHandler
@H_
489_0
@methodHandler用于处理方法的调用,其
onmessage
方法接收一个MethodCall类型消息,并根据MethodCall的成员变量@H_487_73
@method去调用对应的API,当处理完成后,根据方法调用成功或失败,返回对应的结果。
1.4.3. StreamHandler
@H_
489_0@
@H_
489_0@binary
messager@H_
489_0@StreamHandler与前两者稍显不同,用于事件流的通信,最为常见的用途就是Platform端向Flutter端发送事件消息。当我们实现一个StreamHandler时,需要实现其
onListen
和
onCancel
方法。而在
onListen
方法的入参中,有一个EventSink(其
在Android是一个对象,iOS端则是一个block)。我们持有EventSink后,即可通过EventSink向Flutter端发送事件消息。@H_
489_0@实际上,StreamHandler工作原理并不复杂。当我们注册了一个StreamHandler后,实际上会注册一个对应的Binary
messageHandler到Binary
messager。而当Flutter端开始监听事件时,会发送一个二进制消息到Platform端。Platform端用MethodCodec将该消息解码为MethodCall,如果MethodCall的method的值为"listen",则调用StreamHandler的
onListen
方法,传递给StreamHandler一个EventSink。而通过EventSink向Flutter端发送消息时,实际上就是通过Binary
messager的send方法将消息传递过去。
2. 理解消息编解码过程
@H_
489_0@在官方文档《Wri
Ting custom platform-specific code with platform ch
Annels》中的获取设备电量的例子中我们发现,Android端的返回值是
java.lang.Integer
类型的,而iOS端返回值则是一个
NSnumber
类型的(通过
NSnumber numberWithInt:
获取)。而到了Flutter端时,这个返回值自动"变成"了dart语言的int类型。那么这中间发生了什么呢?@H_
489_0@Flutter官方文档表示,
standard platform chAnnels
使用
standard messsage codec
对
message
和
response
进行序列化和反序列化,
message
与
response
可以是
Booleans
,
numbers
,
Strings
,
byte buffers
,
List
, @H_487_73
@maps等等,而序列化后得到的则是二进制格式的数据。@H_
489_0@所以在上文提到的例子中,
java.lang.Integer
或
NSnumber
类型的返回值先是被序列化成了一段二进制格式的数据,然后该数据传递到传递到flutter侧后,被反序列化成了dart语言中的
int
类型的数据。@H_
489_0@Flutter默认的消息编解码器是Standard
messageCodec,其支持的数据类型如下:@H_
489_0@
@H_
489_0@binary
messager@H_
489_0@当
message或response需要被编码为二进制数据时,会调用Standard
messageCodec的
writeValue
方法,该方法接收一个名为
value
的参数,并根据其类型,向二进制数据容器(NSMutableData或ByteArrayOutputStream)写入该类型对应的type值,再将该数据转化为二进制表示,并写入二进制数据容器。@H_
489_0@而
message或者response需要被解码时,使用的是Standard
messageCodec的readValue方法,该方法接收到二进制格式数据后,会先读取一个byte表示其type,再根据其type将二进制数据转化为对应的数据类型。@H_
489_0@在获取设备电量的例子中,假设设备的电量为100,当这个值被转化为二进制数据时,会先向二进制数据容器写入int类型对应的type值:3,再写入由电量值100转化而得的4个byte。而当Flutter端接收到该二进制数据时,先读取第一个byte值,并根据其值得出该数据为int类型,接着,读取紧跟其后的4个byte,并将其转化为dart类型的int。@H_
489_0@
@H_
489_0@binary
messager@H_
489_0@对于字符串、列表、字典的编码会稍微复杂一些。字符串使用UTF-8编码得到的二进制数据是长度不定的,因此会在写入type后,先写入一个代表二进制
数据长度的size,再写入数据。列表和字典则是写入type后,先写入一个代表列表或字典中元素个数的size,再递归调用
writeValue
方法将其元素依次写入。
3. 理解消息传递过程
@H_
489_0@消息是如何从Flutter端传递到Platform端的呢?接下来我们以一次MethodCh
Annel的调用为例,去理解消息的传递过程。
3.1. 消息传递:从Flutter到Platform
3.1.1. Dart层
@H_
489_0@当我们在Flutter端使用MethodCh
Annel的
invokeMethod
方法发起一次方法调用时,就开始了我们的消息传递之旅。
invokeMethod
方法会将其入参
message
和
arguments
封装成一个MethodCall对象,并使用MethodCodec将其编码为二进制格式数据,再通过Binary
messages将消息发出。(注意,此处提到的类名与方法名均为dart层的实现)@H_
489_0@上述过程最终会调用到ui.Window的
_sendPlatformmessage
方法,该方法是一个native方法,其实现
在Native层,这与Java的JNI技术
非常类似。我们向native层发送了三个参数:
- @H_489_0@
name
,String类型,代表ChAnnel名称 - @H_489_0@
data
,ByteData类型,即之前封装的二进制数据 - @H_489_0@
callBACk
,Function类型,用于结果回调
3.1.2. Native层
@H_
489_0@到native层后,window.cc的SendPlatform
message方法接受了来自dart层的三个参数,并对它们
做了一定的处理:dart层的回调
callBACk
封装为native层的Platform
messageResponseDart类型的
response
;dart层的二进制数据
data
转化为std::vector<uint8_t>类型数据
data
;根据
response
,
data
以及Ch
Annel名称
name
创建一个Platform
message对象,并通过
dart_state->window()->client()->HandlePlatformmessage
方法处理Platform
message对象。@H_
489_0@
dart_state->window()->client()
是一个WindowClient,而其具体的实现为RuntimeController,RuntimeController会将消息交给其代理RuntimeDelegate处理。@H_
489_0@RuntimeDelegate的实现为
ENGIne,
ENGIne
在处理message时,会判断该消息是否是为了获取资源(ch
Annel等于"flutter/assets"),如果是,则走获取资源逻辑,否则调用
ENGIne::Delegate的
OnENGIneHandlePlatformmessage
方法。@H_
489_0@
ENGIne::Delegate的具体实现为S
Hell,其
OnENGIneHandlePlatformmessage
接收到消息后,会向PlatformTaskRunner添加一个Task,该Task会调用PlatformView的
HandlePlatformmessage
方法。值得注意的是,Task中的代码执行
在PLatform Task Runner中,而之前的代码均执行在UI Task Runner中。@H_
489_0@
@H_
489_0@binary
messager
3.2. 消息处理
@H_
489_0@PlatformView的
HandlePlatformmessage
方法在不同平台有不同的实现,但是其基本原理是相同的。
3.2.1. PlatformViewAndroid
@H_
489_0@PlatformViewAndroid的是Platformview的子类,也是其
在Android端的具体实现。当PlatformViewAndroid接收到Platform
message类型的消息时,如果消息中有
response
(类型为Platform
messageResponseDart),则生成一个自增长的
response_id
,并以
response_id
为key,
response
为value存入字典
pending_responses_
中。接着,将
chAnnel
和
data
均转化
为Java可识别的数据,通过JNI向Java层发起调用,将
response_id
、
chAnnel
和
data
传递过去。@H_
489_0@Java层中,被调用的代码为FlutterNativeView (Binary
messager的具体实现)的
handlePlatformmessage
,该方法会根据
chAnnel
找到对应的Binary
messageHandler并将消息传递给它处理。其具体处理过程我们已经在上文中详细分析过了,此处不再赘述。@H_
489_0@Binary
messageHandler处理完成后,FlutterNativeView会通过JNI调用native的方法,将
response_data
和
response_id
传递到native层。@H_
489_0@native层,PlatformViewAndroid的
InvokePlatformmessageResponseCallBACk
接收到了
respond_id
和
response_data
。其先将
response_data
转化为二进制结果,并根据
response_id
,从
panding_responses_
中找到对应的Platform
messageResponseDart对象,调用其
Complete
方法将二进制结果返回。@H_
489_0@
@H_
489_0@binary
messager
3.2.2. PlatformViewIOS
@H_
489_0@PlatformViewIOS是PlatformView的子类,也是其在iOS端的具体实现,当PlatformViewIOS接收到
message时会交给Platform
messageRouter处理。@H_
489_0@Platform
messageRouter通过Platform
message中的
chAnnel
找到对应的FlutterBinary
messageHandler,并将二进制消息其处理,消息处理完成后,直接调用Platform
message对象中的Platform
messageResponseDart对象的
Complete
方法将二进制结果返回。
3.3. 结果回传:从Platform到Flutter
@H_
489_0@Platform
messageResponseDart的
Complete
方法向UI Task Runner添加了
一个新的Task,这个Task的作用是将二进制结果从native的二进制数据类型转化为Dart的二进制数据类型
response
,并调用dart的call
BACk将
response
传递到Dart层。@H_
489_0@Dart层接收到二进制数据后,使用MethodCodec将数据解码,
并返回给业务层。至此,一次从Flutter发起的方法调用就完整结束了。
4. 问题解析
4.1. Platform ChAnnel的代码运行在什么线程
@H_
489_0@在文章
《深入理解Flutter引擎线程模型》中提及,Flutter
ENGIne自己不创建线程,其线程的创建于管理是由enbedder提供的,并且Flutter
ENGIne要求Embedder提供四个Task Runner,分别是Platform Task Runner,UI Task Runner,GPU Task Runner和IO Task Runner。@H_
489_0@实际上,
在PLatform侧执行的代码运行
在PLatform Task Runner中,而在Flutter app侧的代码则运行在UI Task Runner中。
在Android和iOS平台上,Platform Task Runner跑在主线程上。因此,不应该
在PLatform端的Handler中处理耗时操作。
4.2. Platform ChAnnel是否线程安全
@H_
489_0@Platform Ch
Annel并非是线程安全的,这一点在官方的文档也有提及。Flutter
ENGIne中多个组件是非线程安全的,故跟Flutter
ENGIne的所有交互(接口调用)必须发生
在PLatform Thread。故我们在将Platform端的消息处理结果回传到Flutter端时,需要确保回调函数是
在PLatform Thread(也就是Android和iOS的主线程)中执行的。
4.3. 是否支持大内存数据块的传递
@H_
489_0@Platform Ch
Annel实际上是支持大内存数据块的传递,当需要传递大内存数据块时,需要使用Basic
messageCh
Annel以及BinaryCodec。而整个数据传递的过程中,唯一可能出现数据拷贝的位置为native二进制数据转化为Dart语言二进制数据。若二进制数据大于阈值时(目前阈值为1000byte)则不会拷贝数据,直接转化,否则拷贝一份再转化。
4.4. 如何将Platform ChAnnel原理应用到开发工作中
@H_
489_0@实际上Platform Ch
Annel的应用场景非常多,我们这里举一个例子:@H_
489_0@在平常的业务开发中,我们需要使用到一些本地图片资源,但是Flutter端是
无法使用Platform端已存在的图片资源的。当Flutter端需要使用一个Platform端已有的图片资源时,只有将该图片资源拷贝一份到Flutter的Assert目录下才能使用。实际上,让Flutter端使用Platform端的资源并不是一件难事。@H_
489_0@我们可以使用Basic
messageCh
Annel来完成
这个工作。Flutter端将图片资源名name传递给Platform端,Native端使用Platform端接收到name后,根据name定位到图片资源,并将该图片资源以二进制数据格式,通过Basic
messageCh
Annel,传递回Flutter端。@H_
489_0@在Flutter与Native混合开发的模式下,Platform Ch
Annel的应用场景非常多,理解Platform Ch
Annel的工作原理,有助于我们在从事这方面开发时能做到得心应手。@H_
489_0@最后,闲鱼技术团队广招各类方向的达人,无论你是精通移动端,前端,后台,还是机器学习,音视频,自动化测试等,都欢迎投递简历
加入我们,一同用技术改善生活!@H_
489_0@简历投递:
guicai.gxy@alibaba-inc.com@H_
489_0@
https://flutter.io/platform-channels/@H_
489_0@
https://github.com/flutter/flutter@H_
489_0@
https://github.com/flutter/engine@H_
489_0@
作者:闲鱼技术
大佬总结
以上是大佬教程为你收集整理的Flutter框架:深入理解Flutter Platform Channel全部内容,希望文章能够帮你解决Flutter框架:深入理解Flutter Platform Channel所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。