使用工具来分析Flutter的性能瓶颈

使用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相关信息

img

  1. 动作右侧的彩色数字为GPU operation id,多个命令共享一个id时,他们将被处理在一起。
  2. Display GPU Op Bounds将显示一个彩色的矩阵,表示当前GUP在操作时候的边界。
  3. GPU,表示Skia用哪个硬件进行配置绘制。在wasm调试器中,GPU表示WebGL。关闭后则使用CPU,skia会在内存中绘制平面,再将内存的图片拷贝到HTML的图层上,而不使用GPU。

十字线和断点

img

点击屏幕后会出现十字线,可以通过H(左)L右)J(向下)K(向上)来移动位置。

选择“Break on change”时,命令播放将在任何更改所选像素颜色的命令上暂停。这可用于查找绘制您在查看器中看到的内容的命令。

右侧操作栏

Histogram:点击后隐藏不需要的动作

通过在filter textbox中输入!drawannotation save restore concat setmatrix cliprect可以过滤matrix或clip操作,这些动作不会带来可视化的变化

快捷键

使用“,”与“。”可以逐帧显示操作(页面中有提示)。

列表帧数优化

闲鱼-Flutter 流畅度优化实践总结

渲染过程

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是官方的开发配套工具,非常实用

  1. Performance检测单帧CPU耗时(build、layout、paint)、GPU耗时、Widget Build次数
  2. CPUProfiler 检测方法耗时
  3. Flutter Inspector观察不合理布局
  4. 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个事件:

这里我们在 handleBeginFrame 处理之前,记录一帧开始事件,在 handleDrawFrame 之后记录一帧的结束。这里每一帧都需要计算列表控件 offset 值,具体代码实现见右图。在整个累计超过 1s 时,执行一次计算,使用 offset 过滤掉没有发生滚动的场景,使用每一帧的时间计算 fps 值。

卡顿堆栈监测

基本原理是,在 C 层轮询发送信号,比如 5ms 一次,每次信号接收触发 dart UI Thread 堆栈采集,对得到的一系列堆栈进行聚合,连续多次相同堆栈就认为是发生了卡顿,这时这个堆栈就是我们想要的卡顿堆栈。

检测过度渲染

通过复写 WidgetsFlutterBinding 的 buildOwner 方法替换 BuildOwner 对象,进而重写 scheduleBuildFor 方法,实现拦截脏 element。基于脏 element 节点,提取出脏节点的深度、直接子节点的数量、全部子节点的数量。

基于全部子节点数量,在闲鱼详情页,我们定位到“快速提问视图”在滚动过程中,频繁被标脏和全部子节点数量过大。查看代码,定位该视图层级过高,通过将视图下沉到叶子节点,一次标脏 build 节点数量从 255 优化至 43。

调试经验

在观看Flutter 的性能测试和理论后,得出的结论: