Cross-platform development savior - Let's take a look at flutter

The first article of friends can see my column, occasionally releases Android interview content, and so on advanced topics.

Brief introduction

A lot of people have to spend a flutter, today to introduce the

Flutter architecture

image

image

Flutter framework divided into three layers
Framework, Engine, Embedder

Framework uses dart language, including UI, text, pictures, buttons, etc. Widgets, rendering, animation, and other gestures. This part of the core code is io the flutter and flutter and warehouses under Package, and sky_engine warehouses, async, ui (dart: ui library provides an interface between the frame and the engine Flutter) like package.

Engine using C ++ implementation, including: Skia, Dart and Text.

  • Skia is open source two-dimensional graphics library that provides a common API for a variety of hardware and software platforms. It has as Google Chrome, Chrome OS, Android, Mozilla Firefox, Firefox OS and so many other products, graphics engine, support platform also includes Windows, macOS, iOS, Android, Ubuntu and so on.

  • Dart part includes: Dart Runtime, Garbage Collection (GC), then if Debug mode, further comprising a JIT (Just In Time) support. Release Profile and lower patterns are AOT (Ahead Of Time) compiled into native code for the arm, there is no portion JIT.

  • Text text rendering i.e., which render the level as follows: the libtxt library derived from Minikin (for font selection, line separator); HartBuzz glyph for selecting and forming; as rendering the Skia / GPU backend using FreeType rendered on Android and Fuchsia using CoreGraphics on iOS to render fonts.

Embedder是一个嵌入层,通过该层把Flutter嵌入到各个平台上去,Embedder的主要工作包括渲染Surface设置, 线程设置,以及插件等。平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。

Dart语言

Dart 也是一种VM语言,所以在每个运行flutter的app中都有一个dart的运行环境。编译模式支持AOT和JIT。
Dart最开始是google设计出来替代javascript的,但是并没有凑效。后面Flutter选择了Dart, 才使Dart活跃起来。

Dart语言的特点:

  • 单进程异步事件模型

  • 强类型,可以类型推断

  • 具有极高的运行效率和优秀的代码运行优化的VM,根据早前的基准测试,性能比肩 Java7 的JVM;

  • 独特的隔离区( Isolate ),可以实现多线程

  • 面向对象编程,一切数据类型均派生自 Object

  • 运算符重载,泛型支持

  • 强大的 Future 和 Stream 模型,可以简单实现高效的代码

  • Minix 特性,可以更好的实现方法复用

  • 全平台语言,可以很好的胜任移动和前后端的开发

  • 在语法上,Dart 提供了很多便捷的操作

Flutter线程管理

Flutter Engine自己不创建, 管理线程。Flutter Engine线程的创建和管理是由embedder负责的

Embeder提供四个Task Runner, 每个Task Runner负责不同的任务,Flutter Engine不在乎Task Runner具体跑在哪个线程,但是它需要线程配置在整一个生命周期里面保持稳定。也就是说一个Runner最好始终保持在同一线程运行

Platform Task Runner

是Flutter Engine的主Task Runner,运行Platform Task Runner的线程可以理解为是主线程。类似于Android Main Thread或者iOS的Main Thread。对于Flutter Engine来说Platform Runner所在的线程跟其它线程并没有实质上的区别。 可以同时启动多个Engine实例,每个Engine对应一个Platform Runner,每个Runner跑在各自的线程里。这也是Fuchsia(Google正在开发的操作引擎)里Content Handler的工作原理。一般情况下,一个Flutter应用启动的时候会创建一个Engine实例,Engine创建的时候会创建一个线程供Platform Runner使用。

跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread,试图在其它线程中调用Flutter Engine会导致无法预期的异常。这跟Android和IOS对于UI的操作都必须在主线程进行相类似。需要注意的是在Flutter Engine中有很多模块都是非线程安全的。一旦引擎正常启动运行起来,所有引擎API调用都将在Platform Thread里发生。

Platform Runner所在的Thread不仅仅处理与Engine交互,它还处理来自平台的消息。这样的处理比较方便的,因为几乎所有引擎的调用都只有在Platform Thread进行才能是安全的,Native Plugins不必要做额外的线程操作就可以保证操作能够在Platform Thread进行。如果Plugin自己启动了额外的线程,那么它需要负责将返回结果派发回Platform Thread以便Dart能够安全地处理。规则很简单,对于Flutter Engine的接口调用都需保证在Platform Thread进行。

阻塞Platform Thread不会直接导致Flutter应用的卡顿(跟iOS android主线程不同)。尽管如此,平台对Platform Thread还是有强制执行限制。所以建议复杂计算逻辑操作不要放在Platform Thread而是放在其它线程(不包括我们现在讨论的这个四个线程)。其他线程处理完毕后将结果转发回Platform Thread。长时间卡住Platform Thread应用有可能会被系统Watchdot强行杀死。

UI Task Runner

Flutter Engine用于执行Dart root isolate代码。Root isolate比较特殊,它绑定了不少Flutter需要的函数方法。Root isolate运行应用的main code。引擎启动的时候为其增加了必要的绑定,使其具备调度提交渲染帧的能力。

  1. 对于每一帧,引擎要做的事情有:

  2. Root isolate通知Flutter Engine有帧需要渲染。

  3. Flutter Engine通知平台,需要在下一个vsync的时候得到通知。

  4. 平台等待下一个vsync

  5. 对创建的对象和Widgets进行Layout并生成一个Layer Tree,Layer Tree马上被提交给Flutter Engine。当前阶段没有进行任何光栅化,这个步骤仅是生成了对需要绘制内容的描述。

  6. 创建或者更新Tree,这个Tree包含了用于屏幕上显示Widgets的语义信息。这个东西主要用于平台相关的辅助Accessibility元素的配置和渲染。

除了渲染相关逻辑之外Root Isolate还是处理来自Native Plugins的消息响应,Timers,MicroTasks和异步IO。
Root Isolate负责创建管理的Layer Tree最终决定什么内容要绘制到屏幕上。因此这个线程的过载会直接导致卡顿掉帧。
如果确实有无法避免的繁重计算,建议将其放到独立的Isolate去执行,比如使用compute关键字或者放到非Root Isolate,这样可以避免应用UI卡顿。但是需要注意的是非Root Isolate缺少Flutter引擎需要的一些函数绑定,你无法在这个Isolate直接与Flutter Engine交互。所以只在需要大量计算的时候采用独立Isolate。

GPU Task Runner

用于执行设备GPU的相关调用。UI Task Runner创建的Layer Tree信息是平台不相关,也就是说Layer Tree提供了绘制所需要的信息,具体如何实现绘制取决于具体平台和方式,可以是OpenGL,Vulkan,软件绘制或者其他Skia配置的绘图实现。GPU Task Runner中的模块负责将Layer Tree提供的信息转化为实际的GPU指令。GPU Task Runner同时也负责配置管理每一帧绘制所需要的GPU资源,这包括平台Framebuffer的创建,Surface生命周期管理,保证Texture和Buffers在绘制的时候是可用的。

基于Layer Tree的处理时长和GPU帧显示到屏幕的耗时,GPU Task Runner可能会延迟下一帧在UI Task Runner的调度。一般来说UI Runner和GPU Runner跑在不同的线程。存在这种可能,UI Runner在已经准备好了下一帧的情况下,GPU Runner却还正在向GPU提交上一帧。这种延迟调度机制确保不让UI Runner分配过多的任务给GPU Runner。

GPU Runner可以导致UI Runner的帧调度的延迟,GPU Runner的过载会导致Flutter应用的卡顿。一般来说用户没有机会向GPU Runner直接提交任务,因为平台和Dart代码都无法跑进GPU Runner。但是Embeder还是可以向GPU Runner提交任务的。因此建议为每一个Engine实例都新建一个专用的GPU Runner线程。

IO Task Runner

主要功能是从图片存储(比如磁盘)中读取压缩的图片格式,将图片数据进行处理为GPU Runner的渲染做好准备。在Texture的准备过程中,IO Runner首先要读取压缩的图片二进制数据(比如PNG,JPEG),将其解压转换成GPU能够处理的格式然后将数据上传到GPU。这些复杂操作如果跑在GPU线程的话会导致Flutter应用UI卡顿。但是只有GPU Runner能够访问GPU,所以IO Runner模块在引擎启动的时候配置了一个特殊的Context,这个Context跟GPU Runner使用的Context在同一个ShareGroup。事实上图片数据的读取和解压是可以放到一个线程池里面去做的,但是这个Context的访问只能在特定线程才能保证安全。这也是为什么需要有一个专门的Runner来处理IO任务的原因。获取诸如ui.Image这样的资源只有通过async call,当这个调用发生的时候Flutter Framework告诉IO Runner进行刚刚提到的那些图片异步操作。这样GPU Runner可以使用IO Runner准备好的图片数据而不用进行额外的操作。

用户操作,无论是Dart Code还是Native Plugins都是没有办法直接访问IO Runner。尽管Embeder可以将一些一般复杂任务调度到IO Runner,这不会直接导致Flutter应用卡顿,但是可能会导致图片和其它一些资源加载的延迟间接影响性能。所以建议为IO Runner创建一个专用的线程

android & iOS平台上面每一个Engine实例启动的时候会为UI,GPU,IO Runner各自创建一个新的线程。所有Engine实例共享同一个Platform Runner线程

isolate

image

image

An isolated Dart execution context

isolate是Dart对actor并发模式的实现。运行中的Dart程序由一个或多个actor组成,actor也就是Dart概念里面的isolate。isolate是隔离的,每个isolate有自己的内存和单线程运行的实体. isolate之间不互相共享内存,且独立GC。
isolate中的代码是顺序执行的,且是单线程,所以不存在资源竞争和变量状态同步的问题,也就不需要锁。Dart中的并发都是多个isolate并行实现的

由于isolate不共享内存,所以isolate之间不能直接互相通信,只能通过Port进行通信,而且是异步的

Flutter Engine Runners与Dart Isolate

Dart的Isolate是Dart虚拟机自己管理的,Flutter Engine无法直接访问。Root Isolate通过Dart的C++调用能力把UI渲染相关的任务提交到UI Runner执行, 这样就可以跟Flutter Engine相关模块进行交互,Flutter UI相关的任务也被提交到UI Runner也可以相应的给Isolate一些事件通知,UI Runner同时也处理来自App方面Native Plugin的任务。 Dart isolate跟Flutter Runner是相互独立的,它们通过任务调度机制相互协作。

Dart内存管理

Dart VM将内存管理分为新生代(New Generation)和老年代(Old Generation)

  • 新生代:初次分配的对象都位于新生代中,该区域主要是存放内存较小并且生命周期较短的对象,比如局部变量。新生代会频繁执行内存回收(GC),回收采用“复制-清除”算法,将内存分为两块,运行时每次只使用其中的一块,另一块备用。当发生GC时,将当前使用的内存块中存活的对象拷贝到备用内存块中,然后清除当前使用内存块,最后,交换两块内存的角色。

  • 老年代: 在新生代的GC中“幸存”下来的对象,它们会被转移到老年代中。老年代存放生命力周期较长,内存较大的对象。老年代的GC回收采用“标记-清除”算法,分成标记和清除两个阶段。在标记阶段会触发停顿,多线程并发的完成对垃圾对象的标记,降低标记阶段耗时。在清理阶段,由GC线程负责清理回收对象,和应用线程同时执行,不影响应用运行。

Flutter中的image所占的内存

Android将中内存分java内存或native内存,通常在代码中的申请的内存都在这两个范围内

java内存是指java或kotlin分配的内存对象
native内存是指由C/C++中分配的内存,也包括一些android原生系统占用的内存,如图像资源和其他图形等

Flutter中的image占用的不用这两种内存,而是Graphics内存,Graphics内存内存是指图形缓冲区队列向屏幕显示像素所使用的内存,图形缓冲区是指GL表面,GL纹理等。Graphics内存是与CPU共享的内存,而不是GPU专用的内存

Flutter运行模式

Flutter常见的种运行模式:Debug,Release和Profile

Release和Profile模式比较类似,不用之处在于Profile模式的服务扩展的支持,支持跟踪,以及最小化使用跟踪信息需要的依赖。Profile并不支持模拟器,原因在于模拟器上的诊断并不代表真实的性能。所有重点截介绍
Debug和Release的差异

  • Debug模式:使用JIT编译,支持模拟器和设备。打开了断言支持,包括所有的调试信息,服务扩展和Observatory等调试辅助。此模式为快速开发和运行做了优化,但并未对执行速度,包大小和部署做优化。
    所以能实现秒级别的hot reload

  • Release模式:使用AOT编译,只支持真机,不支持模拟器。关闭了所有断言,尽可能多地去掉了调试信息,关闭了所有调试工具。为快速启动,快速执行,包大小做了优化。禁止了所有调试辅助手段,服务扩展。

Flutter Platform Channel

Platform Channel用来实现flutter和Native之间的通讯,实现方式类似远程通讯。

Flutter定义了三种Channel:

  • BasicMessageChannel:用于传递字符串和半结构化的信息

  • MethodChannel:用于传递方法调用(method invocation)

  • EventChannel: 用于数据流(event streams)的通信

这三种channel的工作原理都一致,都用三个基本的属性:

  • name: String类型,代表Channel的名字,也是其唯一标识符

  • Messager:BinaryMessenger类型,代表消息信使,是消息的发送与接收的工具

  • codec: MessageCodec类型或MethodCodec类型,代表消息的编解码器

BinaryMessenger是Native端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。

BinaryMessenger只和BinaryMessageHandler通讯。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。

当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端。

Codec:息编解码器,主要用来将二进制格式的数据转化为Handler能够识别的数据,Flutter定义了两种Codec:MessageCodec和MethodCodec

MessageCodec用于二进制格式数据与基础数据之间的编码和解码。有多重实现如:BinaryCodec, StringCodec, JSONMessageCodec等

MethodCodec用于二进制数据与方法调用(MethodCall)和返回结果之间的编解码。MethodChannel和EventChannel所使用的编解码器均为MethodCodec。

MethodCodec codec for MethodCall object, an object that represents a call from MethodCall method initiates the Flutter. MethodCall has two member variables: type String method name of method representatives need to call the method generic type (Android as Object, iOS for id) on behalf of the arguments need to call to the Senate.

Since the processing method is invoked, MethodCodec more calls to deal with the results. When the method call is successful, the result using encodeSuccessEnvelope encoded as a binary data, and when the method call fails, encodeErrorEnvelope of the error code is used, message, detail encoded binary data.

MethodCodec implemented in two: JSONMethodCodec and StandardMethodCodec

Since Platform Channel run UI Task Runner flutter App, the corresponding operation achieved in the native Platform Task Runner, Platform Task Runner while the main thread, so that the native implementation is time-consuming operation can not be performed, and not thread safe Platform Task Runner , so make sure the callback function executes in the main thread

When Platform Channel support large data transfer, the data transfer large blocks of memory, and using BasicMessageChannel BinaryCodec. The entire data transfer process, the only possible location for the copied data native binary data into binary data for Dart. If the binary data is larger than the threshold value (current threshold 1000byte) does not copy data, direct conversion, or reconversion copy.

Learn

See here, you understand yet? Like what the point of a support


Guess you like

Origin blog.51cto.com/14606040/2458201