Flutter 性能调优之性能分析工具与 trace 介绍

0 评论 2114 浏览 0 收藏 20 分钟

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

Flutter 性能调优之性能分析工具与 trace 介绍-华为开发者话题 | 华为开发者联盟

Flutter 应用在鸿蒙(OpenHarmony)平台上的性能问题,通常表现为滑动卡顿、点击无反馈、页面加载迟缓等现象。定位这些问题需要理解 Flutter 的渲染流程与线程模型,并掌握 DevEco Studio Profiler 与 Flutter DevTools 两类工具的 trace 采集与解读方法。

       本文是 Flutter 鸿蒙性能定位系列的基础篇,介绍 Flutter 的渲染流程、线程模型、关键 trace 点以及两类性能分析工具的使用方法,为后续具体场景问题的定位提供基础。完成本文阅读后,如需深入定位具体问题,请继续阅读:

       • Flutter性能调优之滑动卡顿问题介绍

       • Flutter性能调优之点击响应和完成时延介绍

1. 前置条件

       • HarmonyOS 系统(5.0及以上版本),前台运行 Flutter 页面;

       • 分析工具:

          o DevEco Studio Profiler

          o Flutter DevTools

       本文截图使用的 Flutter 版本为 3.27.5-ohos-1.0.7

2. Flutter 线程模型

       Flutter 内部有四类线程,各自承担不同职责:

线程名称

职责

性能图层位置

平台线程

平台主线程,插件代码与系统接口调用在此运行。在鸿蒙平台上即为应用主线程

—

UI 线程

在 Dart VM 中执行 Dart 代码,包括开发者代码与 Flutter 框架生成的代码。应用创建和展示场景时,UI 线程首先构建图层树(layer tree),并提交给 GPU 线程渲染。不要阻塞该线程

性能图层下方

Raster 线程

光栅化线程,又称 GPU 线程。拿到 layer tree 后交给图形库(Skia/Impeller)生成 GPU 指令完成光栅化。该线程变慢通常由 UI 线程产出的图层复杂度导致

性能图层上方

I/O 线程

执行高负载 I/O 操作以避免阻塞 UI 或 Raster 线程

不显示

       注意:从 Flutter 3.29 版本起支持 UI 线程与平台线程合并。合并后,UI 线程中原有的 trace 点都会出现在平台线程泳道中。

3. Flutter 渲染流程

       Flutter 采用”UI 线程构建图层树 → Raster 线程光栅化 → 系统合成上屏”的渲染模型。一次完整的帧渲染经过以下阶段:

       一帧渲染流程

3.1 帧调度

       当输入事件到达、动画 tick 触发或 setState 标记 Element 为 dirty 时,Flutter 通过 Animator::RequestFrame 向系统请求 vsync 信号。系统在下一个 vsync 到来时回调 Animator::BeginFrame,UI 线程开始一帧的工作。

3.2 UI 线程:构建图层树

       UI 线程收到 vsync 后执行四个子阶段,输出一棵 LayerTree(图层树):

子阶段

作用

典型耗时来源

Animate(动画)

派发 Ticker/AnimationController 回调,推进动画状态,可能触发 setState

动画回调中的重计算

Build(构建)

调用 dirty Element 的 build() 重建 Widget 树,更新 Element 与 RenderObject 树

超大范围重建、build 中执行同步耗时操作(排序、JSON 解析)

Layout(布局)

对 RenderObject 树执行 measure/layout,计算各节点位置与尺寸

嵌套层级过深、IntrinsicHeight/Width 多次测量

Paint(绘制)

将 RenderObject 树绘制为 LayerTree,记录各图层绘制指令

复杂路径、阴影、saveLayer 调用过多

       四个子阶段均在 UI 线程同步完成,任一阶段超时都会导致该帧 UI 线程耗时过长。构建完成的 LayerTree 通过 Scene 提交给 Engine,由 Raster 线程消费。

3.3 Raster 线程:光栅化

       Raster 线程拿到 LayerTree 后执行 GPURasterizer::Draw:

       1. 通过 binder 向 RenderService 进程申请 buffer(对应 ReuseBuffer 标识),用于存储渲染结果;

       2. 遍历图层树,将绘制指令交由图形库(Skia/Impeller)执行,生成 GPU 指令并写入 buffer;

       3. 渲染完成后释放 buffer,交由系统合成。

       Raster 线程的耗时通常由 UI 线程产出的图层复杂度决定:saveLayer 过多、复杂 Shader、大量图片上传等都会拖慢光栅化。

3.4 系统合成上屏

       • RSMainThread::DoComposition:RenderService 主线程从 buffer 中获取帧数据(对应 acquire buffer),合成渲染树上各节点图层;

       • RSUniRenderThread/RenderFrame:GPU 执行最终绘制,帧上屏。

       UI 线程与 Raster 线程的总耗时超过一帧预算(如 120Hz 下 8.33ms、90Hz 下 11.1ms)时,会导致 RenderService 缺帧,表现为掉帧卡顿。

4. 关键 Trace 点

       下表按渲染流程顺序列出 Flutter 应用性能分析时需要关注的关键 trace 点,用于在 trace 中定位各阶段的起止:

阶段

Trace 标记

所在线程/泳道

系统输入上报

H:originEventHandle code:

mmi_service

应用收到触摸事件

H:DispatchTouchEvent

应用主线程

触摸事件投递至 Flutter

H:flutter::Shell::OnPlatformViewDispatchPointerDataPacket

应用主线程

Flutter UI 线程收到触摸事件

H:flutter::Engine::DispatchPointerDataPacket

ui

请求新一帧绘制

H:flutter::Animator::RequestFrame

ui

开始一帧绘制,生成图层树

H:flutter::Animator::BeginFrame frame_number:

ui

将图层树转为 GPU 指令并光栅化

H:flutter::GPURasterizer::Draw

raster

RenderService 合成各节点图层

H:RSMainThread::DoComposition

render_service

GPU 执行最终绘制

H:RenderFrame

RSUniRenderThread

       说明:UI 线程与平台线程合并后,UI 线程中原有的 trace 点都会出现在平台线程泳道中。

5. 使用 DevEco Studio Profiler 进行性能分析

       DevEco Studio Profiler 是 DevEco Studio 提供的场景化调优工具,其中 Frame 模板可以录制应用运行过程中的关键数据,用于深度分析卡顿丢帧、耗时长等问题。本节说明在 Flutter 性能分析场景下的使用方法,更通用的操作说明可参考Frame分析。

5.1 抓取 trace

       1. 使用 release 或 Profile 构建模式安装应用,连接设备;

       2. 打开 DevEco Studio → Profiler,选择设备与目标应用、进程,选择 Frame 模板,单击 Create Session 创建会话;

       3. 单击启动按钮开始录制,在设备上执行待分析操作,完成后停止录制并保存 trace 文件;

       4. 抓取完成后,收藏应用进程的 ui、raster 线程和 RenderService 主线程泳道,便于后续分析。

       收藏关键泳道

5.2 帧渲染跟踪:标识符匹配

       掌握帧渲染流程(§3)后,还需要掌握流程各单元之间的匹配方法,为每个单元寻找合适的标识符,才能在 trace 上正确串联一帧的完整渲染过程。

第一个标识:frame_number

       ui 与 raster 线程之间的联系标识符是 frame_number。收藏这两个线程并关联观察,会在 trace 上找到该标识符,这是 Flutter 应用帧渲染的第一个标识符:

       frame_number 标识

       • 如果 frame_number 未在 ui 和 raster 中出现,表明当前帧不是 Flutter 自渲染,需要重新定界性能问题。

第二个标识:ReuseBuffer / acquire buffer

       raster 与 render_service 之间的联系标识符是 ReuseBuffer。在 raster 线程 flutter::GPURasterizer::Draw 的 trace 过程中,会向 RenderService 进程申请 buffer 内存,用于存储渲染的帧内容。点击 binder transaction 的 trace 可以跳转到另一线程,查看对应的 ReuseBuffer 编号:

       ReuseBuffer 申请

       在 render_service 线程 RSMainThread::DoComposition 的 trace 过程中,会获取 buffer 内存里的帧数据,acquire buffer sequence 中的编号与 raster 线程申请的 ReuseBuffer 编号一致:

       acquire buffer

       通过以上两个标识,可以识别 Flutter 应用到 RenderService 进程上的帧渲染链路。RenderService 进程上后续单元目前没有直观的 trace 可供识别。

5.3 性能问题定界

       抓取 trace 并收藏关键泳道后,按帧渲染流程定界问题归属。下图 RenderService 缺失帧,是因为 UI 线程单帧耗时较长,超过一帧的时间(120 帧率下,一帧为 8.33 毫秒):

       RenderService 缺失帧

       • 一帧渲染流程中,ui 和 raster 加起来的总时长超过一帧时间,则性能问题可定界为 Flutter 侧问题;

          o 如上图,从 ui 线程代码执行开始(标记①),到 raster 线程代码执行结束(标记②)为止,超过了一帧时间,需进一步定位具体阶段。

5.4 性能问题定位

场景一:Flutter 页面点击卡顿

       以某应用 Flutter 页面点击卡顿为例,定位过程如下:

       1. 使用 Profiler Frame 分析能力抓取该过程的 trace,根据 mmi_service 泳道的 trace 点找到页面滑动过程,并点击 Frame 泳道下的子泳道 Display Vsync 可看到该过程的屏幕刷新率为 89,Flutter 页面各线程的单帧处理时间需控制在 11.1ms(1000/90)以内,否则会出现滑动卡顿问题:

       2. 查看页面滑动附近 ui 线程的 trace,发现 ui 线程在点击操作后flutter::DefaultPointerDataDispatcher::DispatchPacket 耗时 828ms,远超 11.1ms:

       3. 在 Callstack 泳道的 ui[65405] 子泳道中查看 ui 线程单帧绘制时的调用栈,发现耗时主要集中在 libflutter.so 文件,但对应的是 ui 线程中的点击事件,因此问题点在 Dart 代码点击事件的业务逻辑耗时太长:

6. 使用 Flutter DevTools 进行性能分析

       DevEco Studio Profiler 侧重系统侧 trace 分析,Flutter DevTools 侧重 Dart 侧帧级与事件级分析,两者互补。建议先用 Profiler 定界问题归属(UI 线程还是 Raster 线程),再用 DevTools 深入 Dart 侧定位具体函数。

6.1 devtools 使用方式

       1. 连接真实设备,以 Profile 模式运行 Flutter 应用;

       2. 运行成功后,用浏览器打开 log 中输出的 devtools 调试地址,如 http://127.0.0.1:9104?uri=http:// 127.0.0.1:54358/。

6.2 打开 performance overlay

       在命令行中按 P 可以切换应用中 performance overlay 视图的显示或隐藏。下面的截图展示了 Flutter Gallery 样例的性能图层:

       performance overlay

       性能图层显示 Raster 线程(上)和 UI 线程(下)的叠加图,垂直的绿色条代表当前帧。红色竖条表明当前帧的渲染和绘制都很耗时,当两张图表都是红色时,应优先对 UI 线程(Dart VM)进行诊断。

6.3 性能问题定界

       打开 DevTools 中的 Performance 界面,当在 “Flutter frames” 视图中看到一个红色或耗时较长的帧时,可这样操作:

       1. 点击有问题的帧,下方的时间线事件区域会同步更新,显示该帧内发生的所有事件:

       2. 在时间线中,会看到按线程(如 UI 线程、GPU 线程)分类的事件流,事件以色块形式展示,色块宽度代表

       其耗时:

       3. 寻找耗时最长的事件。UI 线程中异常的宽色块(例如一个 build 事件)通常就是性能瓶颈所在,可悬停或点击事件查看其详细信息与调用栈:

6.4 常见应用层问题与对应 trace 事件

UI 线程常见问题

       1. Build 阶段耗时过长:这是最常见的问题。火焰图中 Build 相关色块又宽又大,通常意味着 build() 方法执行了耗时同步操作(如大数据排序、JSON 解析)或触发了超大范围的不必要重建。

          o 排查方法:开启 Track Widget Builds 选项,Timeline 里会显示具体是哪个 Widget 的 build 方法被调用;结合 CPU Profiler 的火焰图或调用树,定位到具体耗时函数。

       2. Layout 阶段耗时:可能源于嵌套层级过深、使用了 IntrinsicHeight/Width 等需要多次测量布局的组件。

Raster 线程常见问题

       1. saveLayer() 调用过多:这是 Raster 线程性能下降的头号元凶。saveLayer() 会创建离屏缓冲区,非常消耗性能。DevTools 的 Frame Analysis 标签页会直接给出类似 “Canvas.saveLayer() was called 16 times during this frame” 的警告。

          o 应用层原因:过度使用 Opacity、ClipPath、ShaderMask 或复杂的半透明重叠效果,这些都会触发隐式 saveLayer() 调用。

       2. 复杂的图形效果:过度使用阴影(PhysicalModel)、大量图片绘制等。

相关文档

       • Flutter 性能调优之滑动卡顿问题介绍 (系列第二篇)

       • Flutter 性能调优之点击响应和完成时延介绍 (系列第三篇)

DevEco Studio Profiler 调优工具介绍

       • DevEco Profiler 调优工具简介

       • 使用 Profiler 进行性能调优

       • Flutter 页面滑动卡顿问题分析

Flutter DevTools 调优介绍

       • Flutter 性能评估

       • Flutter 性能视图

       • Flutter 性能分析

🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5

     🔗 社区DFX专题文章:  https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001

【扫码加入 HarmonyOS DFX 技术交流群】

本文由 @华为开发者联盟 授权发布于人人都是产品经理。未经作者许可,禁止转载

题图来自Unsplash,基于CC0协议

该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
海报
评论
评论请登录
  1. 目前还没评论,等你发挥!