原理
Flutter的架构主要分成三层:Framework,Engine和Embedder
-
Framework 使用 dart 实现,包括 Material Design 风格的Widget,Cupertino(针对iOS)风格的Widgets,UI/文本/图片/按钮等基础 Widgets,渲染,动画,手势等。此部分的核心代码是: flutter 仓库下的flutter package,以及 sky_engine 仓库下的 io, async, ui (dart:ui 库提供了 Flutter 框架和引擎之间的接口)等package。
-
Engine使用C++实现,主要包括: Skia, Dart 和 Text。
- Skia是开源的二维图形库,提供了适用于多种软硬件平台的通用API。其已作为Google Chrome,Chrome OS,Android, Mozilla Firefox, Firefox OS等其他众多产品的图形引擎,支持平台还包括Windows, macOS, iOS,Android,Ubuntu等。
- Dart 部分主要包括:Dart Runtime,Garbage Collection(GC),如果是Debug模式的话,还包括JIT(Just In Time)支持。Release和Profile模式下,是AOT(Ahead Of Time)编译成了原生的arm代码,并不存在JIT部分。
- Text 即文本渲染,其渲染层次如下:衍生自 Minikin 的 libtxt 库(用于字体选择,分隔行);HartBuzz用于字形选择和成型;Skia作为渲染/GPU后端,在Android和Fuchsia上使用FreeType渲染,在iOS上使用CoreGraphics来渲染字体。
-
Embedder 是一个嵌入层,即把 Flutter 嵌入到各个平台上去,这里做的主要工作包括渲染 Surface 设置,线程设置,以及插件等。从这里可以看出,Flutter 的平台相关层很低,平台(如iOS)只是提
-
供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。
Flutter线程
Flutter里面有四个线程分别是Platform Task Runner 、UI Task Runner、GPU Task Runner 和 IO Task Runner。
- Platform Task Runner:也就是 Android 和 iOS 的主线程
- UI Task Runner:就是Flutter的 UI 线程
- GPU Task Runner:GPU线程被用于执行设备GPU的相关调用
- IO Task Runner:IO线程主要功能是从图片存储(比如磁中读取压缩的图片格式,将图片数据进行处理为GPU Runner的渲染做好准备
主要关注UI线程和GPU线程的性能问题。
Flutter视图树
Flutter视图树包含了三颗树:Widget、Element、RenderObject
-
Widget: 存放渲染内容、它只是一个配置数据结构,创建是非常轻量的,在页面刷新的过程中随时会重建
-
Element: 同时持有Widget和RenderObject,存放上下文信息,通过它来遍历视图树,支撑UI结构
Build函数的参数BuildContext,实际是Element -
RenderObject: 根据Widget的布局属性进行layout,paint ,负责真正的渲染
从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
Flutter运行模式
Flutter有四种运行模式:Debug,Release,Profile,test
-
Debug模式可以在真机和模拟器上同时运行:会打开所有的断言,包括debugging信息、debugger aids(比如observatory)和服务扩展。优化了快速develop/run循环,但是没有优化执行速度、二进制大小和部署。命令flutter run就是以这种模式运行的,通过sky/tools/gn --android或者sky/tools/gn --ios来build。有时候也被叫做“checked模式”或者“slow模式”。
-
Release模式只能在真机上运行,不能在模拟器上运行:会关闭所有断言和debugging信息,关闭所有debugger工具。优化了快速启动、快速执行和减小包体积。禁用所有的debugging aids和服务扩展。这个模式是为了部署给最终的用户使用。命令flutter run --release就是以这种模式运行的,通过sky/tools/gn --android --runtime-mode=release或者sky/tools/gn --ios --runtime-mode=release来build。
-
Profile模式只能在真机上运行,不能在模拟器上运行:基本和Release模式一致,除了启用了服务扩展和tracing,以及一些为了最低限度支持tracing运行的东西(比如可以连接observatory到进程)。命令flutter run --profile就是以这种模式运行的,通过sky/tools/gn --android --runtime-mode=profile或者sky/tools/gn --ios --runtime-mode=profile```来build。因为模拟器不能代表真实场景,所以不能在模拟器上运行。
-
test模式只能在桌面上运行:基本和Debug模式一致,除了是headless的而且你能在桌面运行。命令flutter test就是以这种模式运行的,通过sky/tools/gn来build。
建议在Profile模式下测试性能,在Android Studio中通过菜单启动Profile模式:
性能指标
1、流畅度
Github上与流畅度相关的讨论:流畅度
页面帧率
Flutter 在全局 Window 对象上提供了帧回调机制。我们可以在 Window 对象上注册 onReportTimings 方法,将最近绘制帧耗费的时间(即 FrameTiming),以回调的形式告诉我们。
有了每一帧的绘制时间后,我们就可以计算 FPS 了。
为了让 FPS 的计算更加平滑,我们需要保留最近 25 个 FrameTiming 用于求和计算。
由于帧的渲染是依靠 VSync【VSync是垂直同期(Vertical Synchronization)的简称,基本的思路是将你的FPS和显示器的刷新率同期起来。其目的是避免一种称之为"撕裂"的现象。】 信号驱动的,如果帧绘制的时间没有超过 16.67 ms,我们也需要把它当成 16.67 ms 来算,因为绘制完成的帧必须要等到下一次 VSync 信号来了之后才能渲染。而如果帧绘制时间超过了 16.67 ms,则会占用后续 VSync 的信号周期,从而打乱后续的绘制次序,产生卡顿现象。
那么,页面帧率的统计公式就是:
FPS = 60 * 实际渲染的帧数 / 本来应该在这个时间内渲染完成的帧数。
页面加载时长
统计页面可见的时间:WidgetsBinding 提供了单次 Frame 回调的 addPostFrameCallback 方法,它会在当前 Frame 绘制完成之后进行回调,并且只会回调一次。一旦监听到 Frame 绘制完成回调后,我们就可以确认页面已经被渲染出来了,因此我们可以借助这个方法去获取页面的渲染完成时间 endTime。
统计页面创建的时间:获取页面创建的时间比较容易,我们只需要在页面的初始化函数 initState() 里记录页面的创建时间 startTime。
最后,再将这两个时间做减法,你就能得到页面的加载时长。需要注意的是,正常的页面加载时长一般都不应该超过2秒。如果超过了,则意味着有严重的性能问题。
启动速度
2、内存
Github上与内存相关的讨论:内存
能够复用的对象尽量复用,不使用的内存应尽早回收,降低APP内存消耗
3、应用大小
Github上与包大小相关的讨论:应用大小
4、功耗
Github上与功耗相关的讨论:功耗
工具
Flutter DevTools:包含了Inspector和Performance的功能,这两部分功能在下面单独介绍
Flutter Inspector:用于可视化和查看 widget 树
调试布局问题,请在Debug 模式下运行应用程序,然后点击 DevTools 工具栏上的 Flutter inspector 选项打开调试面板。
主要功能包括:
- Select Widget Mode:选择 widget 模式,启用此按钮以在设备上选择 widget 进行查看。
- 刷新树:重新加载当前 widget 的信息。
- Slow Animations:慢速动画,以五分之一的速度运行动画以便对它们进行优化。
- Show Guidelines:显示引导线,覆盖一层引导线以帮助调整布局问题。
- Show Baselines: 显示基线,针对文字对齐展示文字的基线。对检查文字是否对齐有帮助。
- Highlight Repaints:高亮重绘制内容,重新绘制时在图层上依次显示不同的颜色。
- Highlight Oversized Images:高亮尺寸过大的图片,在运行的应用程序中高亮并反转消耗过多内存的图像。
参考文档:跳转
Flutter Performance
详细可参考官网文档:使用性能视图
- Performance Overlay:性能图层用两张图表显示应用的耗时信息。每一张图表都代表当前线程的最近 300 帧表现。点击按钮打开该功能。
PerformanceOverlay 可以视为 Performance Overlay 的高级版本(Performance Overlay 正是基于 PerformanceOverlay 控件来实现的)。
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
showPerformanceOverlay: true,
title: 'My Awesome App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'My Awesome App'),
);
}
}
如果没有使用 MaterialApp 等控件,可以自己调用 PerformanceOverlay.allEnabled(checkerboardOffscreenLayers: true,); 来实现类似效果。
- 顶部的图形表示 GPU 线程,即Raster线程所花费的时间:GPU 线程执行 Flutter 引擎中图形相关的代码。某些图层树易于构建却难于渲染,也可能导致这个线程变慢
- 底部的图表显示了 UI 线程所花费的时间:UI 线程执行 Dart VM 中的 Dart 代码。构建过于复杂的图层树可能导致这个线程变慢
- 竖轴表示耗时,沿竖轴的黑线是时间线 (间隔单位为 16ms)
- 横轴则表示帧,垂直的绿色条代表的是当前帧
- 卡顿时绿色条会变成红色条:如果是在 UI 图表出现了红色竖条,则表明 Dart 代码消耗了大量资源而如果红色竖条是在 GPU 图表出现的,意味着场景太复杂导致无法快速渲染
最佳实践
1、
实战
列表页在上下滑动时,从查看性能帧常出现红色条,说明存在性能问题。从现象看,每帧在出现图片时的帧率性能不佳。从而怀疑是图片渲染导致的性能问题。
列表功能截图如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20d30ae3f5414fc8a0a1e3a9a7951607.png
尝试以下优化方法:
1、