ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

在 Flutter Devicelab 中用 Android 模拟器测试 Android 变更:基于 .ci.yaml 的配置指南

在 Flutter Devicelab 中用 Android 模拟器测试 Android 变更:基于 .ci.yaml 的配置指南 在 Flutter Devicelab 中用 Android 模拟器测试 Android 变更基于 .ci.yaml 的配置指南【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutterFlutter 的 Devicelab 传统上运行在真实物理设备上但为了降低对真机设备池的依赖仓库已经通过 LUCI recipes 加入了对「Android 模拟器」的支持开发者只需在仓库根目录的 .ci.yaml 中声明测试目标基础设施即可自动完成模拟器的创建与测试驱动。本文以官方文档 Testing-Android-Changes-in-the-Devicelab-on-an-Emulator.md 为主线结合仓库中真实的.ci.yaml配置与 Devicelab 框架源码讲解如何从零新增一个跑在模拟器上的测试目标以及如何把一个既有真机目标迁移到模拟器环境。为什么需要「模拟器版」DevicelabDevicelab代码位于 dev/devicelab是一套跑在真实设备上的测试体系其任务tasks通常要求实验室中存在对应类型的物理设备如linux_android表示 Linux 主机连接着 Android 真机。物理设备池数量有限、维护成本高且无法覆盖所有开发者本地场景。因此 Flutter 为 Devicelab 增加了模拟器运行通道在 LUCI recipe 层先启动一个 Android 模拟器Android Virtual Device, AVD再让测试任务在模拟器上执行。开发者只需在 CI 配置文件里新增一个 target就能在任意 PR 或提交上自动验证 Android 侧的框架 / 引擎改动无需手动申请真机。在动手前可以先了解两条相关联的资料路径Flutter-Self-Service-Index.md 收录了本文档的索引入口How-to-add-a-new-integration-test-to-Framework-CI.md 描述了向框架 CI 添加集成测试目标的通用流程其中同样提及devicelab_dronerecipe可作为旁证。前置认知.ci.yaml 与 Devicelab 任务的协作关系仓库根目录的 .ci.yaml 是 Flutter 持续集成目标target的清单文件其头部注释说明了它的作用与约束enabled_branches该文件只对master及flutter-\d\.\d-candidate\.\d形式的发布候选分支生效platform_properties为若干「平台组」如linux、linux_android_emu、mac、windows预置公共属性依赖、机器要求等后续大量以- name:开头的条目则是具体要执行的 CI 目标。而每个 Devicelab 测试任务的「本体」是一个 Dart 程序存放在 dev/devicelab/bin/tasks 下通过package:flutter_devicelab/framework/framework.dart注册并执行参考 dev/devicelab/README.md 的 Writing tests 一节。目标名称与task_name共同决定最终执行哪一个任务文件。此外任务在 TESTOWNERS 中登记维护者例如示例任务android_defines_test就登记在第 14 行归属jesswrd flutter/android。新增一个模拟器测试目标完整 YAML 与字段逐项解析原文档给出了一套「从零新增 Devicelab Android 模拟器测试」的完整目标定义。原文中的引号存在中文字符“1”、true混用在真实 YAML 解析中必须使用 ASCII 双引号下面给出规范化后的可复制版本- name: Linux_android android_defines_test recipe: devicelab/devicelab_drone presubmit: true timeout: 60 dimensions: { kvm: 1, cores: 8, Machine_name: n1-standard-8 } properties: device_type: none task_name: android_defines_test use_emulator: true dependencies: - [ {dependency: android_virtual_device, version: 31} ] tags: [devicelab, linux] timeout: 300name平台前缀与目标命名name的前缀部分标明平台可选值为Linux或Linux_Android。原文档建议优先选择Linux_Android因为它会为任务提供更多 Android 相关的预装依赖SDK、模拟器等后续接模拟器所需的依赖更齐全。需要注意随着仓库 CI 配置演进当前 .ci.yaml 中面向模拟器的目标实际以Linux_android_emu为前缀例如第 2336 行的Linux_android_emu android_defines_test该前缀与platform_properties中预置好的模拟器平台组一一对应。这一点在下一节详述。recipe固定为 devicelab/devicelab_dronerecipe: devicelab/devicelab_dronerecipe永远填写devicelab/devicelab_drone。这是唯一负责「启动模拟器并驱动测试」的 recipe它会先按依赖声明拉起 Android 模拟器再通过 Devicelab 的测试 runner 执行task_name指定的任务。这一约定同样体现在 .ci.yaml 的所有模拟器目标中。presubmit是否在 PR 中触发presubmit: true取值为true或false。如果希望在任意打开的 PR 上都运行该测试就设为true——这是把 Android 改动在镜像到 google3 之前提前暴露问题的最佳手段。若设false则只在 CI 提交post-submit上运行。顶层 timeout任务超时整数timeout: 60目标层级的timeout是整数表示 LUCI 为该目标分配的整体超时预算单位通常为分钟用于兜底避免任务无限期悬挂。当前仓库内模拟器目标的顶层超时多为 60例如Linux_android_emu android_defines_test见 .ci.yaml。dimensions要求嵌套虚拟化dimensions: { kvm: 1, cores: 8, Machine_name: n1-standard-8 }dimensions的作用是告知 LUCI 框架该测试必须被调度到支持嵌套虚拟化的机器上因为模拟器依赖 KVM 加速。因此必须按示例填写kvm: 1机器必须启用 KVM内核虚拟化cores: 8至少 8 核Machine_name: n1-standard-8使用 GCEn1-standard-8型机器该机型在 Flutter 基建中已被验证支持模拟器运行。在当前的 .ci.yamlplatform_properties.linux_android_emu平台组中kvm: 1与cores: 8同样作为公共属性预置见 .ci.yaml印证了该字段的必要性。properties任务级关键开关properties是驱动 recipe 行为的核心共包含六项a.device_type: none必须设为none表示使用一台未外接 Android 手机的机器。若不设置recipe 会尝试寻找已连接的 Android 设备并可能报错从而引发问题。b.task_name任务的名称对应bin/tasks/{task_name}.dart中的任务文件名去掉.dart后缀。例如本示例的android_defines_test对应 dev/devicelab/bin/tasks/android_defines_test.dart该任务会声明deviceOperatingSystem DeviceOperatingSystem.android并执行dartDefinesTask()见 dev/devicelab/lib/tasks/integration_tests.dart用于验证 Android 上--dart-define是否生效。c.use_emulator: true告诉测试 recipe 需要为该测试创建一个模拟器。注意这里是字符串true而非布尔值true。d.dependencies覆盖 AVD 版本dependencies: - [ {dependency: android_virtual_device, version: 31} ]dependencies可用于覆盖android_virtual_device的 API 版本。此处version: 31表示希望使用 API 31 的系统镜像若某平台组已默认声明依赖则可省略这一项详见下文“更新既有目标”。e.tags: [devicelab, linux]tags应设置为devicelab与linux供仪表盘与调度逻辑归类任务。f. properties 内层 timeouttimeout: 300这是给测试本身运行的时间预算超时即被终止与目标层timeout是两回事。为便于速查将上述字段整理为对照表字段位置取值/写法作用name目标顶层Linux或Linux_Android 空格 任务名决定平台前缀与可读命名Linux_Android携带更全的 Android 依赖recipe目标顶层恒为devicelab/devicelab_drone负责创建模拟器并驱动测试presubmit目标顶层true/false是否在 PR 上运行timeout目标顶层整数分钟LUCI 整体任务超时dimensions目标顶层kvm:1、cores:8、Machine_name:n1-standard-8要求支持嵌套虚拟化的机器device_typepropertiesnone使用无外接真机的机器缺省会引发问题task_namepropertiesbin/tasks/*.dart去掉后缀的名字指定要执行的 Devicelab 任务use_emulatorproperties字符串true要求 recipe 创建模拟器dependenciespropertiesandroid_virtual_device API 版本覆盖 AVD 系统镜像版本tagsproperties[devicelab,linux]任务归类标签timeoutproperties整数测试运行时间预算超时杀掉当前仓库的真实落地形态platform_properties 与 Linux_android_emu原文档描述的机制在目标内联声明dimensions、use_emulator、AVD 依赖等在当前仓库中已进一步组织为platform_properties平台组让同一平台的所有目标共享一套模拟器配置。以 .ci.yaml 中的linux_android_emu为例其公共属性包括contexts: [android_virtual_device]为所有该平台目标注入 AVD 上下文device_type: none与文档要求一致kvm: 1、cores: 8嵌套虚拟化与算力要求dependencies默认声明了android_sdk version:37v2、android_virtual_device version:android_36_google_apis_x64.textpb、avd_cipd_version build_id:...、open_jdk 21、clang、cmake、ninja、gradle_dists等完整工具链。同时仓库还维护了两个变体平台组linux_android_emu_unstable.ci.yaml用于验证新的依赖组合组合的稳定性linux_android_emu_vulkan_stable.ci.yamlVulkan 稳定通道其注释明确说明因 API 36 上虚拟显示存在潜在问题对应 Flutter issue #170024暂时改用 API 35 的 AVDandroid_35_google_apis_x64.textpb。从 .ci.yaml 头部注释还可以看到 AVD 配置名的来源说明合法的android_virtual_device版本名可在 Chromium 的tools/android/avd/proto目录下查找而avd_cipd_version可通过 CIPD 页面最新实例中的build_id:Identifier#获取。因此在当前仓库中新增模拟器目标的实际写法更简洁——直接以Linux_android_emu为前缀声明目标即可无需在目标内重复kvm、device_type与 AVD 依赖。真实例子可见 .ci.yaml- name: Linux_android_emu android_defines_test recipe: devicelab/devicelab_drone timeout: 60 properties: tags: [devicelab, linux] task_name: android_defines_test presubmit_max_attempts: 2同类目标还有Linux_android_emu android views.ci.yaml、Linux_android_emu android_display_cutout、Linux_android_emu external_textures_integration_test以及引擎侧渲染测试Linux_android_emu android_engine_opengles_tests、Linux_android_emu_vulkan_stable android_hardware_smoke_vulkan_tests等均可作为仿写模板。把既有真机目标改造成模拟器目标五个步骤如果只是想更新一个既有目标例如把某个跑在 Pixel 真机上的目标迁移到模拟器只需做如下五处修改添加dimensions字段内容如上一节所示声明kvm: 1等嵌套虚拟化要求设置device_type: none确保任务被调度到无外接手机、可自建模拟器的机器在 properties 中添加use_emulator: true再次强调它是字符串而非布尔值true而不是true添加模拟器版本依赖在 properties 的dependencies中加入android_virtual_device条目若目标原本没有dependencies字段则按前文完整示例那样整体新增移除任何标注 android 的 tags如[devicelab, android, ...]。这类 tag 是为基准测试benchmark预留的保留它们会导致部分设备检查device checks失败。改造后的目标同样可以依赖当前仓库中预置的Linux_android_emu平台组使第 1、2、4 步由平台公共属性代为提供目标内只需声明task_name、tags等差异项。框架源码视角use_emulator 在 Devicelab 内部如何生效了解 CI 配置后再深入 Devicelab 框架源码可以看到use_emulator这条链路在任务运行器中的具体处理。在 dev/devicelab/lib/framework/runner.dart 的runTask中存在一个名为useEmulator的参数来自rerunTask/run一路透传。当其为真时运行器会在启动任务进程前向taskArgs追加两个参数if (useEmulator) { taskArgs ?? String[]; taskArgs ..add(--android-emulator) ..add(--browser-nameandroid-chrome); }也就是说模拟器模式会同时附加--android-emulator与--browser-nameandroid-chrome——后者服务于后续基于 Android Chrome 的 Web/集成测试运行。任务侧同样存在对应处理例如 dev/devicelab/lib/tasks/android_views_test.dart 就显式接收这两个参数。任务进程随后在独立的 Dart VM 中运行执行体为bin/tasks/{task_name}.dart。以文档示例android_defines_test为例其入口 dev/devicelab/bin/tasks/android_defines_test.dart 内容非常简短——设置deviceOperatingSystem DeviceOperatingSystem.android后调用dartDefinesTask()具体的构建、安装、运行与校验逻辑封装在 Devicelab 的 task 类库中。设备抽象层位于 dev/devicelab/lib/framework/devices.dart框架内部以AndroidDevice第 607 行起等类封装对 adb 设备的控制唤醒、HOME、锁屏、性能模式等并通过AndroidDeviceDiscovery发现可用设备。作为对照iOS 的物理设备发现逻辑IosDeviceDiscovery第 1000 行附近会调用flutter devices --machine并在筛选中显式排除emulator属性为真的设备——这反过来说明「物理真机」与「模拟器」在 Devicelab 中是两条不同的设备通道Android 模拟器通道正是由devicelab_dronerecipe 在任务运行前提前拉起。本地验证与排障建议虽然模拟器目标最终在 LUCI 上执行但 Devicelab 任务本身完全可以在本地复现详见 dev/devicelab/README.md# 在 dev/devicelab 目录下运行单个任务 ../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test -t {TASK_NAME} # 关闭默认的自动重试便于定位真实失败原因 ../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test --exit -t {TASK_NAME} # 针对本地 engine 构建做 A/B 或对照验证 ../../bin/cache/dart-sdk/bin/dart bin/run.dart --task{TASK_NAME} \ --local-engine-src-path[路径]/engine/src \ --local-engine[本地引擎架构如 android_debug_unopt_x86] \ --local-engine-host[宿主架构如 host_debug_unopt]运行 Android 相关任务前需要保证ANDROID_SDK_ROOT已正确设置可通过flutter doctor -v确认 SDK 位置。Devicelab 的默认失败重试逻辑会自动重跑 2 次若想快速暴露偶发问题请加--exit。针对模拟器目标排查时可重点核对本文列出的五个要点device_type是否为none、use_emulator是否为字符串true、dependencies是否声明了android_virtual_device、dimensions是否包含kvm、tags 是否混入了android。另外若要在 PR 阶段大量使用模拟器任务请留意 Devicelab 的 presubmit 池容量有限dev/devicelab/README.md 的 Adding tests to presubmit 一节建议向team-infra提出 issue 评估可行性。小结在 Devicelab 中用 Android 模拟器验证 Android 侧改动是物理设备池之外一条成本更低、可自动化的回归路径。核心要点可以归结为三句话新增目标时完整声明device_type: none、字符串形式的use_emulator: true以及android_virtual_device依赖并把平台选为携带完整 Android 依赖的Linux_Android当前仓库对应Linux_android_emu平台组更新既有目标时补上dimensions、删掉 android 标签即可完成迁移presubmit: true是在改动镜像到 google3 之前尽早发现缺陷的最佳实践。更详尽的 CI 目标语法可继续研读 .ci.yaml 头部注释及其真实目标条目框架层机制可深入 dev/devicelab/lib/framework/runner.dart 与 dev/devicelab/lib/framework/devices.dart 验证。【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进