
Flutter 渲染速度测试实战macrobenchmarks e2e devicelab 的完整性能基准链路【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter本篇基于 Flutter 仓库中的 How-to-write-a-render-speed-test-for-Flutter 文档展开面向希望暴露或修复 Flutter SDK框架层或引擎层性能问题的贡献者。全文按照“在 macrobenchmarks 中新增测试页 → 编写 e2e 自动化性能测试 → 接入 devicelab 为每个 commit 自动采集指标 → 设置基准线”的完整链路组织并结合 dev/benchmarks/macrobenchmarks 与 dev/devicelab 的当前源码解释每个步骤背后真正的机制测试是如何被驱动的、指标从哪里来、CI 如何调度。读完你可以独立地为任意新场景编写一条渲染速度基准测试并让它进入 Flutter 的性能监控体系。需要说明适用边界该流程在移动端Android/iOS上验证最充分Flutter Web 与桌面端的性能测试文档仍在演进中仓库中 Web 基准走的是另一条 lib/web_benchmarks.dart 路线见 macrobenchmarks/README.md。另外原文档中提到的dev/devicelab/manifest.yaml在当前仓库已不存在任务注册信息迁移到了仓库根目录的 .ci.yaml本文第 4 节会给出对应的实操方式。1. 整体链路为什么用 macrobenchmarks e2e devicelab 三层组合原文档给出的核心结论是写一个渲染速度测试推荐组合三个组件——e2e或 Flutter driver在真机上驱动被测页面并采集性能指标dev/benchmarks/macrobenchmarks 应用集中承载所有性能测试场景页面的“被测应用”dev/devicelab为 Flutter 的每一个 commit 自动运行测试把指标上报到 Flutter 的性能看板flutter/cocoon 体系从而快速发现回归或提速。“macro”一词值得注意它表示基准测试的对象是整个 Flutter 框架 引擎的大系统而不是某个微小的 Dart 或 C 函数后者属于 microbenchmark仓库中对应 dev/benchmarks/microbenchmarks。macrobenchmarks 的设计哲学是**“只加页面不加应用”**它本身就是一个包含大量页面、每个页面对应一个特定性能场景的 Flutter 应用并提供了样板代码和自动生成文件。当需要测试一个新场景时贡献者只需往仓库里添加一个页面和少量文件而不必新建一个带几十个自动生成文件的 Flutter 应用。从源码结构看这种“单应用多路由”的组织方式体现在三处约定上每个场景页一个独立文件放在 dev/benchmarks/macrobenchmarks/lib/src如 simple_scroll.dart、picture_cache.dart、backdrop_filter.dart所有路由名以k*RouteName常量集中在 lib/common.dart例如kCubicBezierRouteName /cubic_bezier首页 lib/main.dart 中的HomePage用一列按钮把这些路由串起来——这些按钮既是给手动测试者的入口也是给 Flutter driver / e2e 的“可点击锚点”靠Key(routeName)定位。以下按原文档假设我们要为某个super_important_case场景编写渲染速度测试。2. 第一步在 macrobenchmarks 中添加一个测试页原文档给出的四个步骤是在 macrobenchmarks/lib/src 下创建super_important_case.dart定义SuperImportantCasePage extends StatelessWidget。如果存在一个只有单个main.dart就能复现性能问题的最小 Flutter 应用通常可以直接把那个main.dart的内容复制进来作为页面内容在 macrobenchmarks/lib/common.dart 中添加路由常量const String kSuperImportantCaseRouteName /super_important_case;打开 macrobenchmarks/lib/main.dart在MacrobenchmarksApp的routes表中注册该路由在HomePage的ListView中添加一个按钮供手动测试者和 Flutter driver 点击跳转到新页面。对照当前仓库源码第 3、4 步的真实形态值得展开。MacrobenchmarksApp在 lib/main.dart 中定义MaterialApp的routes表把每个k*RouteName映射到对应页面例如routes: String, WidgetBuilder{ /: (BuildContext context) const HomePage(), kCullOpacityRouteName: (BuildContext context) const CullOpacityPage(), kCubicBezierRouteName: (BuildContext context) const CubicBezierPage(), // ... 约 50 个场景路由 kTextShadowPerfRouteName: (BuildContext context) const TextShadowPerfPage(), },HomePagelib/main.dart的body是一个ListView(key: const Key(kScrollableName), ...)kScrollableName定义在 lib/common.dart 中值为/macrobenchmark_listview。列表里每个场景对应一个按钮当前代码中的形态是ElevatedButton( key: const Key(kCubicBezierRouteName), child: const Text(Cubic Bezier), onPressed: () { Navigator.pushNamed(context, kCubicBezierRouteName); }, ),一个重要的实操细节原文档示例中使用的是RaisedButton而当前仓库代码已全部迁移到ElevatedButtonRaisedButton已废弃。新增页面时应跟随现有代码使用ElevatedButton但“key: Key(路由名)Navigator.pushNamed”的写法保持不变——后面第 3 节会看到测试框架正是靠这个 Key 找到按钮并点击跳转的。3. 第二步编写 e2e 性能测试3.1 宿主脚本与测试模板当页面完成并手动验证后就可以编写自动化集成测试来采集性能指标。原文档要求宿主侧脚本固定使用 macrobenchmarks/test_driver/e2e_test.dart——所有 e2e 性能测试都依赖它若要修改必须先与其他 Flutter 成员讨论在 macrobenchmarks/test 目录下新增super_important_case_e2e.dart调用macroPerfTestE2E函数它会导航到目标页面并开始采集指标。原文档给出的模板其中pageDelay、duration、timeout、body、setup五个参数均为可选import package:flutter/gestures.dart; import package:flutter/widgets.dart; import package:flutter/foundation.dart; import package:flutter_test/flutter_test.dart; import package:macrobenchmarks/common.dart; import util.dart; void main() { macroPerfTestE2E( super_important_case, kSuperImportantCaseRouteName, /* optional */ pageDelay: const Duration(seconds: 1), /* optional */ duration: const Duration(seconds: 3), /* optional */ timeout: const Duration(seconds: 30), /* optional */ body: (WidgetController controller) async { ... }, /* optional */ setup: (WidgetController controller) async { ... }, ); }各参数的含义继承自原文档参数作用pageDelay页面加载等待时间默认不等待duration性能指标的采样时长timeout测试包的兜底超时原文档指向testWidgets的 timeout 概念body基准测试期间驱动页面的自定义操作如滚动列表与duration同时使用时测试执行到两者中更长者结束setup基准测试开始前的初始化操作3.2 源码佐证macroPerfTestE2E实际做了什么当前实现位于 test/util.dart。它委托给macroPerfTestMultiPageE2E核心逻辑比模板代码透露的更值得理解真实帧模式binding.framePolicy LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive;——让集成测试绑定以“基准测试用真实渲染”策略运行而不是测试常用的伪帧pump 模拟帧这才能保证指标反映真实设备上的渲染行为250 毫秒初始延迟注释写明“避免在设备高负载期开始计时否则基准测试噪声更大”对应 flutter/flutter#19434定位与跳转通过find.byKey(ValueKey(kScrollableName))找到首页ListView内的Scrollable再用scrollUntilVisibletap点击Key(routeName)对应的按钮进入目标页——这解释了第 2 节中“按钮 Key 必须等于路由名”的约定指标采集窗口真正采样的段落是await binding.watchPerformance(() async { ... })窗口内并行执行body(tester)与duration延时即以“两者取长者”作为采样区间与原文档对bodyduration语义的描述一致。顺带说明参数差异当前macroPerfTestE2E签名test/util.dart暴露pageDelay、duration默认 3 秒、body、setuptimeout由testWidgets的Timeout.none接管驱动/测试框架层面有独立超时控制原文档中的timeout:参数名可以视为该机制在文档撰写时期的表述。若你的仓库版本签名不同以本地util.dart实际签名为准。3.3 真实用例无 body 与带 body 的两种写法最简写法见 test/cubic_bezier_perf_e2e.dart——仅一行调用页面自身就是持续动画无需额外驱动void main() { macroPerfTestE2E(cubic_bezier_perf, kCubicBezierRouteName); }带body的完整示例见 test/picture_cache_perf_e2e.dart设置 1 秒pageDelay然后在body中对内部嵌套的 TabBarView 做 3 轮左右往返拖拽模拟用户滚动以触发 picture cache 的真实压力macroPerfTestE2E( picture_cache_perf, kPictureCacheRouteName, pageDelay: const Duration(seconds: 1), body: (WidgetController controller) async { final Finder nestedScroll find.byKey(const ValueKeyString(tabbar_view)); expect(nestedScroll, findsOneWidget); Futurevoid scrollOnce(double offset) async { await controller.timedDrag(nestedScroll, Offset(offset, 0.0), const Duration(milliseconds: 300)); await Futurevoid.delayed(const Duration(milliseconds: 500)); } for (var i 0; i 3; i 1) { await scrollOnce(-300.0); await scrollOnce(-300.0); await scrollOnce(300.0); await scrollOnce(300.0); } }, );3.4 本地运行与指标输出上述步骤完成后在 macrobenchmarks 目录内运行flutter drive --profile -t test/super_important_case_e2e.dart --driver test_driver/e2e_test.dart宿主脚本 test_driver/e2e_test.dart 的全部内容只有 5 行有效代码它通过integrationDriver启动应用并在responseDataCallback中把设备侧回传的performance数据以testOutputFilename: e2e_perf_summary写出——也就是说测试结束后指标会落在当前目录临时build目录下的e2e_perf_summary.json。macrobenchmarks/README.md 对 E2E 基准的运行命令给出的通用形式与之一致flutter drive --profile -t test/[test_name]_e2e.dart --driver test_driver/e2e_test.dart。该 JSON 中常用指标继承自原文档average_frame_build_time_millisaverage_frame_rasterization_time_millisworst_frame_build_time_millisworst_frame_rasterization_time_millis补充一个源码层面的背景帧指标来自引擎时间轴中 UI 线程 build/layout/paint 与光栅化线程耗时的统计build time对应 Dart 侧构建/布局/绘制帧的耗时rasterization time对应引擎光栅化线程的耗时二者共同解释一帧是否“丢帧”。4. 2a. 传统 driver 测试已废弃保留说明原文档标注该路径为deprecated若第 2 步的 e2e 方式已足够则跳过。这里完整继承其内容以备查设备侧应用固定使用 macrobenchmarks/test_driver/run_app.dart其他测试都依赖它改动前需先与 Flutter 成员讨论在 macrobenchmarks/test_driver 下添加super_important_case_perf_test.dart调用macroPerfTestimport package:flutter_driver/flutter_driver.dart; import package:macrobenchmarks/common.dart; import util.dart; void main() { macroPerfTest( super_important_case, kSuperImportantCaseRouteName, pageDelay: const Duration(seconds: 1), /* optional */ driverOps: (FlutterDriver driver) async { ... }, /* optional */ setupOps: (FlutterDriver driver) async { ... }, ); }其中driverOps提供基准测试期间驱动页面的自定义操作如滚动setupOps提供基准测试开始前的操作在 macrobenchmarks 目录内运行flutter drive -t test_driver/run_app.dart --driver test_driver/super_important_case_perf.dart指标写入临时build目录下的super_important_case_perf__timeline_summary.json对比e2e 路径写入e2e_perf_summary.json同样包含average_frame_build_time_millis、average_frame_rasterization_time_millis、worst_frame_build_time_millis、worst_frame_rasterization_time_millis等指标。driver 版的实现可对照 test_driver/util.dartmacroPerfTest通过driver.traceAction包裹采样窗口TimelineSummary.summarize后写出testName.timeline_summary.json与 e2e 版“body 与 duration 取长者”的语义相同await durationFuture与driverOps并行。当前仓库的 bin/tasks 中仍并存*_perf__timeline_summary.dart与*_perf__e2e_summary.dart两类 devicelab 任务说明旧路线在存量场景上仍在运行新场景建议直接用 e2e 路线。5. 第三步更新 README原文档要求把新测试加入 macrobenchmarks/README.md 的列表。当前 README 的结构是“Mobile benchmarksdriver 类逐一列出[test_name]与命令 E2E benchmarks列出 E2E 化测试清单与运行命令”新增 e2e 测试时应在 “E2E benchmarks” 一节补上你的test/[test_name]_e2e.dart并保证文档中的命令与第 3.4 节一致。6. 第四步接入 devicelab 实现每 commit 自动采集原文档的论证是保持 Flutter 高性能靠“偶尔本地跑一次再人工看指标”是不够的必须让 devicelab 在每个 Flutter commit 上自动运行该测试才能快速发现super_important_case的回归或提速。原文档的步骤为在 devicelab 的任务清单中注册super_important_case_perf__e2e_summary原文档写的是dev/devicelab/manifest.yaml参照其他任务设置描述、选择 agent如linux/android的 Moto G4 或mac/ios的 iPhone 6s并标记flaky: true——在观察期不阻塞构建树在 dev/devicelab/bin/tasks 添加super_important_case_perf__e2e_summary.dartimport dart:async; import package:flutter_devicelab/tasks/perf_tests.dart; import package:flutter_devicelab/framework/adb.dart; import package:flutter_devicelab/framework/framework.dart; Futurevoid main() async { deviceOperatingSystem DeviceOperatingSystem.android; // or ios await task(createSuperImportantCasePerfE2ETest()); }在 dev/devicelab/lib/tasks/perf_tests.dart 中添加任务工厂函数TaskFunction createSuperImportantCasePerfE2ETest() { return PerfTest.e2e( ${flutterDirectory.path}/dev/benchmarks/macrobenchmarks, test/super_important_case_e2e.dart, ).run; }在 dev/devicelab 目录、连接 Android 或 iOS 真机的情况下本地验证../../bin/cache/dart-sdk/bin/dart bin/run.dart -t super_important_case_perf__e2e_summary应看到成功与指标摘要输出提交包含以上全部改动的 pull request当测试稳定运行几天后移除flaky: true。原文档还建议创建一个日历提醒因为这往往需要等一段时间。与当前仓库的差异重要本仓库中dev/devicelab/manifest.yaml已不存在任务注册位于仓库根目录的 .ci.yaml。e2e 性能任务在那里以如下形态出现以 backdrop_filter 为例见 .ci.yaml- name: Linux_mokey backdrop_filter_perf__e2e_summary recipe: devicelab/devicelab_drone presubmit: false timeout: 60 properties: tags: [devicelab, android, linux, mokey] task_name: backdrop_filter_perf__e2e_summary其中task_name与bin/tasks下的文件名对应如 backdrop_filter_perf__e2e_summary.dart其内容就是设置deviceOperatingSystem后调用task(createBackdropFilterPerfE2ETest())与原文档模板一致。因此在本仓库落地时步骤 1 应改为“在.ci.yaml中按现有*_e2e_summary条目格式添加任务条目tags 标明平台与设备”其余步骤不变。PerfTest.e2e的参数佐证dev/devicelab/lib/tasks/perf_tests.dart 中PerfTest有两个构造器普通PerfTestdriver 路线需要timelineFileName、testDriver与PerfTest.e2ee2e 路线testDriver默认就是test_driver/e2e_test.dartresultFilename默认为e2e_perf_summary——正好对应第 3.4 节的输出文件名。从源码结构看benchmarkScoreKeys的默认值_kCommonScoreKeys即包含average_frame_build_time_millis、worst_frame_build_time_millis、90th/99th_percentile_frame_build_time_millis、average_frame_rasterizer_time_millis等一系列键见 perf_tests.dart 的注释这说明 devicelab 上报给看板的指标集正是原文档列举的四项“常用指标”的超集。7. 第五步设置基准线benchmark baseline原文档最后一步任务会在 devicelab 上自动运行结果展示在 Flutter 性能看板flutter-dashboard当新测试积累足够数据后在看板上设置 baseline。对于vsync_transitions_missed这类指标需把单位从默认的 ms 改为 frames帧或其他合适单位。这一步是纯看板操作仓库内无对应代码但它是链路闭环的关键没有 baselinedevicelab 采集到的每个 commit 的指标就无法判断“是回归还是正常波动”。8. 小结与仓库延伸阅读把整条链路串起来lib/src/*.dart场景页lib/common.dart路由常量lib/main.dart路由表与首页按钮→test/*_e2e.dart采样窗口与驱动操作test_driver/e2e_test.dart宿主脚本固定复用→devicelab/bin/tasks/*_e2e_summary.dartlib/tasks/perf_tests.dart的PerfTest.e2e.ci.yaml任务条目 → 性能看板上的 baseline 与回归告警。原文档在结尾对完成全部步骤的贡献者表达了祝贺——完成这条链路确实是对 Flutter 性能体系的一次实质性贡献也鼓励贡献者持续改进本文档本身。延伸阅读均为仓库内相对路径dev/benchmarks/macrobenchmarks/README.mddriver 与 E2E 基准的本地运行命令、Web 基准lib/src/web下 raw/widget/widget-build 三类 recorder写法dev/benchmarks/macrobenchmarks/lib/common.dart全部路由常量与kScrollableName的定义dev/benchmarks/macrobenchmarks/test/util.dartmacroPerfTestE2E/macroPerfTestMultiPageE2E完整实现dev/devicelab/lib/tasks/perf_tests.dartPerfTest/PerfTest.e2e全部参数与指标键说明.ci.yaml所有*_e2e_summary任务的注册格式与 agent 选择dev/contributing/testingFlutter 仓库测试相关文档索引。【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考