使用DevTools来调试
# 统计Flutter应用启动时间
flutter run --trace-startup --profile
# 运行profile模式
flutter run --profile --trace-skia
#example
fvm flutter run --profile --trace-skia -d 00008101-000318213A84001E
Flutter run key commands.
v Open Flutter DevTools.
s Save a screenshot to flutter.png.
w Dump widget hierarchy to the console. (debugDumpApp)
t Dump rendering tree to the console. (debugDumpRenderTree)
S Dump accessibility tree in traversal order. (debugDumpSemantics)
U Dump accessibility tree in inverse hit test order. (debugDumpSemantics)
P Toggle performance overlay. (WidgetsApp.showPerformanceOverlay)
a Toggle timeline events for all widget build methods. (debugProfileWidgetBuilds)
M Write SkSL shaders to a unique file in the project directory.
h Repeat this help message.
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on Junhua’s iPhone 12 Max is available at: http://127.0.0.1:50356/k7Mnu3QtEnI=/
The Flutter DevTools debugger and profiler on Junhua’s iPhone 12 Max is available at: http://127.0.0.1:9102?uri=http://127.0.0.1:50356/k7Mnu3QtEnI=/
使用DevTools的Timeline可以看到渲染的耗时情况,红色为慢渲染的动作。
分析Skia运行情况
首先运行DevTools,再生成SKP文件,将文件放入SkiaDebugger中进行解析。
传送门:相关文档
# 运行profile模式
flutter run --profile --trace-skia
# example
fvm flutter run --profile --trace-skia -d 00008101-000318213A84001E
fvm flutter run --profile --trace-skia -d e439ed0f
# 生成SKP文件
flutter screenshot --type=skia --observatory-url=<uri>
# example
fvm flutter screenshot --type=skia --observatory-url=http://127.0.0.1:58856/saNCml2ec4g=/
Screenshot written to flutter_01.skp (34704kB).
SkiaDebugger使用
Flutter将一帧录制成SkPicture(skp)送给Skia进行渲染。捕捉skp,可以进一步分析每一个绘图指令。
GPU相关信息
- 动作右侧的彩色数字为GPU operation id,多个命令共享一个id时,他们将被处理在一起。
- Display GPU Op Bounds将显示一个彩色的矩阵,表示当前GUP在操作时候的边界。
- GPU,表示Skia用哪个硬件进行配置绘制。在wasm调试器中,GPU表示WebGL。关闭后则使用CPU,skia会在内存中绘制平面,再将内存的图片拷贝到HTML的图层上,而不使用GPU。
十字线和断点
点击屏幕后会出现十字线,可以通过H
(左)L
右)J
(向下)K
(向上)来移动位置。
选择“Break on change”时,命令播放将在任何更改所选像素颜色的命令上暂停。这可用于查找绘制您在查看器中看到的内容的命令。
右侧操作栏
Histogram:点击后隐藏不需要的动作
通过在filter textbox中输入!drawannotation save restore concat setmatrix cliprect
可以过滤matrix或clip操作,这些动作不会带来可视化的变化
快捷键
使用“,”与“。”可以逐帧显示操作(页面中有提示)。
列表帧数优化
渲染过程
1、手指松开时,基于 ScrollDragController.end 计算初始速度;
2、UI Thread 向 Platform Thread 请求 requestFrame,在 Platform Thread 收到 Vsync 信息,则向 UI Thread 调用 beginFrame;
3、UI Thread Animate 阶段触发列表滑动一点距离,同时向 Platform Thread 注册下一帧回调;
4、UI Thread Build Widget,再通过 Flutter 三棵树 Diff 算法生成/更新 RenderObject 树;
5、UI Thread RenderObject 树 Layout、Paint 生成 Scene 对象,最后传递给 Raster Thread 进行绘制上屏;
工具
Flutter BenchMark,分析帧数
DevTools:Timeline、CPU燃焰图
DebugFlags:debug.dart
流畅度检测:无需侵入代码的流畅度检测方案有几种, 既可以通过adb取surfaceflinger数据, 也可以基于VirtualDisplay做图像对比,或者使用官方DevTools。第三方比较成熟的如PerfDog
卡顿排查:DevTools是官方的开发配套工具,非常实用
- Performance检测单帧CPU耗时(build、layout、paint)、GPU耗时、Widget Build次数
- CPUProfiler 检测方法耗时
- Flutter Inspector观察不合理布局
- Memory 监控Dart内存情况
脚本测试
检测 Flutter 每帧耗时,需要统计 UI Thread 和 Raster Thread 上的计算耗时。所以 Flutter 优化前后比较,使用 SchedulerBinding.instance.addTimingsCallback
获取每一帧的 UI Thread 和 Raster Thread 的耗时数据。
此外,流畅度性能数值受操作手势、滚动速度影响,所以基于人工操作的测量结果会存在误差。这里使用 WidgetController 控制列表控件 fling。
基于录屏的卡顿测试
闲鱼在 Android 端自研了基于录屏数据的流畅度检测。将手机界面想象成多个画面,通过向系统录屏服务 MediaProjection 注册获取 VirtualDisplay,间隔 16.6 ms读取其中的画面数据(字节数组),这里使用字节数组的 hash 值代表当前画面,当前后 2 次读取的 hash 值不变,则认为发生了卡顿。
线上FPS卡顿监测
在线上场景,闲鱼自研了 Flutter 高可用。基本原理是基于2个事件:
ui.window.onBeginFrame 事件
-
- engine 通知 Vysnc 信号到来,通知 UI Thread 开始准备下一帧画面构建
- 触发 SchedulerBinding.handleBeginFrame 回调
ui.window.onDrawFrame 事件
-
- engine 通知 UI Thread 开始绘制下一帧画面
- 触发SchedulerBinding.handleDrawFrame 回调
这里我们在 handleBeginFrame 处理之前,记录一帧开始事件,在 handleDrawFrame 之后记录一帧的结束。这里每一帧都需要计算列表控件 offset 值,具体代码实现见右图。在整个累计超过 1s 时,执行一次计算,使用 offset 过滤掉没有发生滚动的场景,使用每一帧的时间计算 fps 值。
卡顿堆栈监测
基本原理是,在 C 层轮询发送信号,比如 5ms 一次,每次信号接收触发 dart UI Thread 堆栈采集,对得到的一系列堆栈进行聚合,连续多次相同堆栈就认为是发生了卡顿,这时这个堆栈就是我们想要的卡顿堆栈。
检测过度渲染
通过复写 WidgetsFlutterBinding 的 buildOwner 方法替换 BuildOwner 对象,进而重写 scheduleBuildFor 方法,实现拦截脏 element。基于脏 element 节点,提取出脏节点的深度、直接子节点的数量、全部子节点的数量。
基于全部子节点数量,在闲鱼详情页,我们定位到“快速提问视图”在滚动过程中,频繁被标脏和全部子节点数量过大。查看代码,定位该视图层级过高,通过将视图下沉到叶子节点,一次标脏 build 节点数量从 255 优化至 43。
调试经验
在观看Flutter 的性能测试和理论后,得出的结论:
触发build的几个方式:setState、inheritedWidget、热更新
辅助工具:
-
debugPrintBeginFrameBanner/debugPrintEndFrameBanne
-
- 每帧开始/结束
-
debugPrintScheduleBuildForStacks
-
- 为什么被构建
-
debugPrintRebuildDirtyWidgets
-
- 什么组件被重新构建了
-
debugProfileBuildsEnabled
-
- 在观测台里显示构建树
-
debugDumpLayerTree
-
- 查看layer树
-
debugPaintLayerBordersEnabled
-
- 查看layer界限
-
debugRepaintRainbowEnabled
-
- 被重新绘制的RenderObject
-
debugProfilePaintsEnabled
-
- 在观测台里显示绘制树
优化build方式
-
- 降低遍历出发点(抽离组件,单独更新)
- 停止树的遍历(重用同一组件的实例)
提高paint的效率(合理利用RepaintBounda,界限分明)