Flutter查漏补缺2
Flutter的理念架构
Flutter架构分为三层
- Framework层(dart)
- flutter engine层(C/C++)
- embeder层(platform-specific)
Flutter 被设计为一个可扩展的分层系统。它可以被看作是各个独立的组件的系列合集,上层组件各自依赖下层组件。组件无法越权访问更底层的内容,并且框架层中的各个部分都是可选且可替代的。
Framework层
是由dart编写的sdk,实现了一套基础库,包含了material和Cupertino风格UI界面,下面是一些通用的widget组件,渲染,动画,绘制,手势库等等。这个纯dart实现的库被封装为dart:ui。
Flutter engine层
Flutter引擎是Flutter的核心,主要使用C++编写。它提供了Flutter 核心API底层实现,包括图形渲染(通过Skia)、文本布局、文件以及网络IO(Input/Output ==> 内部与外部的通信)、辅助功能支持、插件架构和Dart运行环境及编译环境的工具链。引擎将底层C++代码包装成Dart代码,通过dart:ui暴露给Flutter框架层。
Skia是Google的一个2D绘图引擎库,Chrome和Android均采用Skia作为绘图引擎,在图形转换、文字渲染、位图渲染方面提供了友好、高效的表现。Skia是跨平台的,可以被嵌入到Flutter的IOS SDK中,而不用去研究IOS闭源的Core Graphics / Core Animation。Android自带了Skia,所以Flutter Android SDK比IOS SDK小的多。
Embeder层
为engine层提供了四个task runner,将引擎移植到平台的中间层代码,渲染设置,原生插件,线程管理,事件循环交互等操作。这一层是操作系统适配层,负责管理线程。
Flutter是如何做到一套Dart代码可以编译运行在Android和iOS平台的
Skia引擎渲染,实现多平台渲染一致性
Method Channel机制
为了解决调用原生系统底层能力以及相关代码库复用问题,Flutter为开发者提供了一个轻量级的解决方案,即逻辑层的方法通道(Method Channel)机制。基于方法通过,我们可以将原生代码所拥有的能力以接口的形式暴露给Dart,从而实现Dart代码和原生代码的交互,就像调用了一个普通的Dart API一样。
注意 Method Channel时非线程安全的。原生代码在处理方法调用请求时,如果涉及到异步或者非主线程切换,需要确保回调过程是在原生系统的UI线程中执行的,否则应用可能会出现奇怪的Bug,甚至是Crash。
关于Platform channel
三种类型
- BasicMessageChannel:用于传递字符串和半结构化的信息
- MethodChannel:用于传递方法调用(method invocation)
- EventChannel:用于数据流(event streams)的通信
成员变量
三种Channel之间互相独立,各有用途,但它们在设计上却非常相近。每种Channel均有三个重要成员变量
- name: String类型,代表Channel的名字,也是其唯一标识符。
- messager:BinaryMessenger类型,代表消息信使,是消息的发送与接收的工具。
- codec: MessageCodec类型或MethodCodec类型,代表消息的编解码器。
如何让Flutter编译出来的APP的包大小尽可能变小
Flutter release产物分析
整个APP由APP Framework和Flutter Framework两部分组成
- APP Framework(可变化的)
- App:Dart代码AOT编译产物,是动态链接库
- flutter_assets:Flutter静态资源文件夹,包括图片,字体等
包体积优化方法
删、缩、挪
- 删:删除无用代码,无用资源。可以手动删除,也可以机器删除,也可以编译时删除。Flutter有一个Tree shaking机制,从Main方法开始,逐级引用,最终没有被引用的代码,诸如类和函数都会被裁减掉。这个就是编译时自动删除。
- 缩:比如压缩图片资源。
- 挪:从项目工程或Packages里直接挪到远端,典型是远端下发插件或者安卓里的App Bundle,虽然“挪”对性能来说是有损的(需要动态下发),但是包体积却大大减少。
移除无用代码和无用资源,压缩图片,安卓里拆App bundle
统一编译参数
在Flutter引擎编译时,安卓和IOS的编译参数不同,安卓时-OZ,IOS时-OS,想追求极致包体积的话,是需要使用-OZ的。升级到最新的build-tools,改OS为OZ
动态下发方案
Flutter工程编译安卓包,会编译成4个snapshot文件,分别是
- isolate_snapshot_instr
- isolate_snapshot_data
- vm_snapshot_instr
- vm_snapshot_data
把isolate_snapshot_data和vm_snapshot_data文件移出来,flutter_assets也移出来。挪动前9.2M,挪动后3.8M,如果App体积庞大,这个收益会更明显。
Flutter渲染优化
重建组件个数尽量少
如果将整个页面写在一个单独的StatefulWidget中,每次setState都会导致不必要的UI重建。拆分组件,使用良好的设计模式和状态管理方案。
构建组件时使用const关键词,可以抑制widget的重建
合理利用const关键词,可以在很大程度上优化应用的性能
需要注意以下两点
- 当const修饰类的构造函数时,要求该类的所有成员必须是final的。适合StatelessWidget。
- const变量只能在定义的时候初始化
有些实现的效果背后可能会使用saveLayer()这个代价很大的方法
- ShaderMask
- ColorFilter
- Chip,当disabledColorAlpha!=0xff时,会调用saveLayer()
- Text,如果有overflowShader,可能调用saveLayer()
官方优化点
由于Opacity会使用屏幕外缓冲区,因此能不用Opacity Widget就尽量不要用。有关透明度应用于图像的示例,请参见Transparent image,比Opacity widget更快,性能更好。
可以通过设置颜色的透明程度,比如Color.fromRGBO(255, 0, 0, 0.5)
Clipping不会调用saveLayer()(除非明确使用Clip.antiAliasWithSaveLayer),因此这些操作没有Opacity那么耗时,但仍旧很耗时,请谨慎使用。
管理着色器编译垃圾
有时候,应用中的动画首次运行会看起来非常卡顿,但是运行多次之后便可以正常运行,这可能就是由于着色器编译混乱导致的。
在不同的平台上,可以执行以下命令,使用SkSL预热功能构建应用程序:
安卓
flutter build apk — bundle-sksl-path flutter_01.sksl.json
IOS
flutter build ios --bundle-sksl-path flutter_01.sksl.json