前言
App 流畅性的关键指标有 UI帧率,GPU帧率,我们期望它能达到 60fps,也就是16ms每帧。
以 profile / release 模式运行
为了获取最接近生产环境的数据,我们应该选择一台尽可能低端的真机,并且以 profile 模式或者 release 模式下运行app。
因为 debug 模式会有一些额外的检查工作,比如
assert()
等为了加速开发效率,debug 模式是以
JIT(Just in time)
模式编译 dart 代码的,而
profile
和release
是提前编译为机器码AOT(Ahead Of Time)
,所以 debug 会慢很多。
所以说我们在查看性能时候不要用debug 模式,之前我就是用debug模式,无论怎么优化,性能都满足不了要求,还以为是flutter自身的问题,但是都说Flutter的渲染效率还是很高的,原来是debug模式的问题。
Flutter运行模式
Debug模式
调试页面开发时使用Profile模式
调试性能 开发时使用Release模式
部署发包时使用
Debug
Debug模式可以在真机和模拟器上同时运行,此模式会打开所有的断言,包括debugging信息、debugger aids(比如observatory)和服务扩展。优化了快速develop/run循环,但是没有优化执行速度、二进制大小和部署。
命令flutter run
就是以这种模式运行的,通过sky/tools/gn --android
或者sky/tools/gn --ios
来构建应用的。
Release
Release模式只能在真机上运行,不能在模拟器上运行:会关闭所有断言和debugging信息,关闭所有debugger工具。优化了快速启动、快速执行和减小包体积。禁用所有的debugging aids和服务扩展。这个模式是为了部署给最终的用户使用。
命令flutter run --release
就是以这种模式运行的,通过sky/tools/gn --android --runtime-mode=release
或者sky/tools/gn --ios --runtime-mode=release
来构建应用。
Profile
Profile模式只能在真机上运行,不能在模拟器上运行,基本和Release模式一致,除了启用了服务扩展和tracing,以及一些为了最低限度支持tracing运行的东西(比如可以连接observatory到进程)。
命令flutter run --profile
就是以这种模式运行的,通过sky/tools/gn --android --runtime-mode=profile
或者sky/tools/gn --ios --runtime-mode=profile
来构建应用。
test
headless test模式只能在桌面上运行,基本和Debug模式一致,除了是headless的而且你能在桌面运行。
命令flutter test
就是以这种模式运行的,通过sky/tools/gn
来build。
怎么使用profile模式呢?
为了调试性能问题,我们需要在发布模式的基础之上,为分析工具提供少量必要的应用追踪信息,这就是分析模式。
除了一些调试性能问题必须的追踪方法之外,Flutter 应用的分析模式和发布模式的编译和运行是类似的,只是启动参数变成了 profile 而已。
我们可以在 Android Studio 中通过菜单栏点击 Run
=>Profile
=>main.dart
选项启动应用,
也可以通过命令行参数 flutter run --profile
运行 Flutter 应用。
Android Studio
菜单栏点击 Run
=> Profile...
注意
该过程第一次编译非常慢请耐心等待,后来就会快很多。
VSCode
打开 launch.json
文件并设置flutterMode
为 profile
1 | "configurations": [ |
命令行
1 | flutter run --profile |
检测帧率
Android Studio中配置
File=>Settings
中搜索flutter
找到
打开Open Flutter Inspector view on app launch
选中 View > Tool Windows > Flutter Performance
.
第一个按钮会在应用中显示,最后按钮一个会减速,方便我们查看帧率
VS Code中配置
选中 View > Command Palette…
会显示一个 command 面板.
在命令面板中输入 performance
并选择 Toggle Performance Overlay
如果命令显示为不可用,需要检查 app 是否正在运行.
代码中配置
在 MaterialApp
或者 WidgetsApp
的构造函数中设置 showPerformanceOverlay
属性为 true
:
1 | class MyApp extends StatelessWidget { |
怎么看帧率
上图演示了性能图层的展现样式。其中,GPU 线程的性能情况在上面,UI 线程的情况显示在下面,蓝色垂直的线条表示已执行的正常帧,绿色的线条代表的是当前帧。
同时,为了保持 60Hz 的刷新频率,GPU 线程与 UI 线程中执行每一帧耗费的时间都应该小于 16ms(1/60 秒)。
在这其中有一帧处理时间过长,就会导致界面卡顿,图表中就会展示出一个红色竖条,如下图所示。
如果红色竖条出现在 GPU 线程图表,意味着渲染的图形太复杂,导致无法快速渲染;而如果是出现在了 UI 线程图表,则表示 Dart 代码消耗了大量资源,需要优化代码执行时间。
图中有三条线,最下面的一条线为16ms,如果应用大部分都在16ms下,就优化的差不多了。
图表分别体现了 UI帧率 和 GPU帧率。如果出现了红色,说明对应的线程有太多work要做。
那先来了解一下 Flutter 中的4个主要线程分别承担了什么职责。
- Platform线程:插件代码运行的线程;即Android/iOS的主线程,
- UI线程:在Dart虚拟机中执行Dart代码。作用是创建视图树,然后将它发送给GPU。注意不要阻塞此线程!
- GPU线程:把上面提到的视图树渲染出来,虽然我们在flutter中不能直接访问GPU线程和数据,但是Dart代码可能导致此线程变慢
- I/O线程:执行比较耗时的任务
在运行app的过程中,观察爆红的地方和触发场景,进行分析。
如果是UI报红:
那么可能是执行了某个较耗时的函数?或者函数调用过多?算法复杂度高?
如果只是 GPU 报红:
那么可能是要绘制的图形过于复杂?或者执行了过多GPU操作?
比如要实现一个混合图层的半透明效果:如果把透明度设置在顶层控件上,CPU会把每个子控件图层渲染出来,再执行
saveLayer
操作保存为一个图层,最后给这个图层设置透明度。而saveLayer
开销很大,这里官方给出了一个建议:首先确认这些效果是否真的有必要;如果有必要,我们可以把透明度设置到每个子控件上,而不是父控件。裁剪操作也是类似。还有一个拖慢GPU渲染速度的是没有给静态图像做缓存,导致每次build都会重新绘制。我们可以把静态图形加到
RepaintBoundry
控件中,引擎会自动判断图像是否复杂到需要用repaint boundary,不需要的话也会忽略。开启saveLayer和图形缓存的检查
1
2
3
4
5
6
7MaterialApp(
showPerformanceOverlay: true,
// 使用了saveLayer的图形会显示为棋盘格式并随着页面刷新而闪烁
checkerboardOffscreenLayers: true,
// 做了缓存的静态图片在刷新页面时不会改变棋盘格的颜色;如果棋盘格颜色变了说明被重新缓存了,这是我们要避免的
checkerboardRasterCacheImages: true,
);
提高流畅性的策略
- 代码调用时机是否可以延后?如底部导航栏式的页面,没有必要第一次进入就把每个子Page都创建出来
- 尽量做到局部刷新
- 把耗时的计算放到独立的isolate去执行
- 检查不必要的 saveLayer
- 检查静态图片是否添加缓存
- relayout boundary:参考
- repaint boundary:参考
内存优化
在内存优化方面,我们的目标是希望减少应用内存占用,减少被系统杀死的概率,同时尽可能的避免内存泄露,减少内存碎片化。
内存优化策略
- 加载对象过大?如图片质量和尺寸不做限制就加载
- 加载对象过多?如加载长列表;在调用频率很高的方法中创建对象
- 合理设置缓存大小/长度
- 在内存不足时或离开页面时清空缓存数据
- 使用ListView.build()来复用子控件
- 自定义绘图中避免在onDraw中做创建对象操作,或者相同的参数设置
- 复用系统提供的资源,比如字符串、图片、动画、样式、颜色、简单布局,在应用中直接引用
- 内存泄露的问题?比如dispose需要销毁的listener等
- 不可见的视图是否也在build?
- 页面离开后的网络请求是否取消?