news 2026/9/7 2:58:30

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 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/flutter

Flutter 的 Devicelab 传统上运行在真实物理设备上,但为了降低对真机设备池的依赖,仓库已经通过 LUCI recipes 加入了对「Android 模拟器」的支持,开发者只需在仓库根目录的 .ci.yaml 中声明测试目标,基础设施即可自动完成模拟器的创建与测试驱动。本文以官方文档 Testing-Android-Changes-in-the-Devicelab-on-an-Emulator.md 为主线,结合仓库中真实的.ci.yaml配置与 Devicelab 框架源码,讲解如何从零新增一个跑在模拟器上的测试目标,以及如何把一个既有真机目标迁移到模拟器环境。

为什么需要「模拟器版」Devicelab

Devicelab(代码位于 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:该文件只对masterflutter-\d+\.\d+-candidate\.\d+形式的发布候选分支生效;
  • platform_properties:为若干「平台组」(如linuxlinux_android_emumacwindows)预置公共属性(依赖、机器要求等);
  • 后续大量以- 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: 300

name:平台前缀与目标命名

name的前缀部分标明平台,可选值为LinuxLinux_Android。原文档建议优先选择Linux_Android,因为它会为任务提供更多 Android 相关的预装依赖(SDK、模拟器等),后续接模拟器所需的依赖更齐全。

需要注意:随着仓库 CI 配置演进,当前 .ci.yaml 中面向模拟器的目标实际以Linux_android_emu为前缀(例如第 2336 行的Linux_android_emu android_defines_test),该前缀与platform_properties中预置好的模拟器平台组一一对应。这一点在下一节详述。

recipe:固定为 devicelab/devicelab_drone

recipe: devicelab/devicelab_drone

recipe永远填写devicelab/devicelab_drone。这是唯一负责「启动模拟器并驱动测试」的 recipe:它会先按依赖声明拉起 Android 模拟器,再通过 Devicelab 的测试 runner 执行task_name指定的任务。这一约定同样体现在 .ci.yaml 的所有模拟器目标中。

presubmit:是否在 PR 中触发

presubmit: true

取值为truefalse。如果希望在任意打开的 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应设置为devicelablinux,供仪表盘与调度逻辑归类任务。

f. properties 内层 timeout

timeout: 300

这是给测试本身运行的时间预算,超时即被终止(与目标层timeout是两回事)。

为便于速查,将上述字段整理为对照表:

字段位置取值/写法作用
name目标顶层LinuxLinux_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_typeproperties"none"使用无外接真机的机器,缺省会引发问题
task_namepropertiesbin/tasks/*.dart去掉后缀的名字指定要执行的 Devicelab 任务
use_emulatorproperties字符串"true"要求 recipe 创建模拟器
dependenciespropertiesandroid_virtual_device+ API 版本覆盖 AVD 系统镜像版本
tagsproperties["devicelab","linux"]任务归类标签
timeoutproperties整数测试运行时间预算,超时杀掉

当前仓库的真实落地形态:platform_properties 与 Linux_android_emu

原文档描述的机制(在目标内联声明dimensionsuse_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:37v2android_virtual_device version:android_36_google_apis_x64.textpbavd_cipd_version build_id:...open_jdk 21clangcmakeninjagradle_dists等完整工具链。

同时仓库还维护了两个变体平台组:

  • linux_android_emu_unstable(.ci.yaml):用于验证新的依赖组合组合的稳定性;
  • linux_android_emu_vulkan_stable(.ci.yaml):Vulkan 稳定通道,其注释明确说明因 API 36 上虚拟显示存在潜在问题(对应 Flutter issue #170024),暂时改用 API 35 的 AVD(android_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为前缀声明目标即可,无需在目标内重复kvmdevice_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_cutoutLinux_android_emu external_textures_integration_test以及引擎侧渲染测试Linux_android_emu android_engine_opengles_testsLinux_android_emu_vulkan_stable android_hardware_smoke_vulkan_tests等,均可作为仿写模板。

把既有真机目标改造成模拟器目标:五个步骤

如果只是想更新一个既有目标(例如把某个跑在 Pixel 真机上的目标迁移到模拟器),只需做如下五处修改:

  1. 添加dimensions字段:内容如上一节所示,声明kvm: "1"等嵌套虚拟化要求;
  2. 设置device_type: "none":确保任务被调度到无外接手机、可自建模拟器的机器;
  3. 在 properties 中添加use_emulator: "true":再次强调,它是字符串而非布尔值("true"而不是true);
  4. 添加模拟器版本依赖:在 properties 的dependencies中加入android_virtual_device条目;若目标原本没有dependencies字段,则按前文完整示例那样整体新增;
  5. 移除任何标注 android 的 tags:如["devicelab", "android", ...]。这类 tag 是为基准测试(benchmark)预留的,保留它们会导致部分设备检查(device checks)失败。

改造后的目标同样可以依赖当前仓库中预置的Linux_android_emu平台组,使第 1、2、4 步由平台公共属性代为提供,目标内只需声明task_nametags等差异项。

框架源码视角: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-name=android-chrome'); }

也就是说,模拟器模式会同时附加--android-emulator--browser-name=android-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是否为noneuse_emulator是否为字符串"true"dependencies是否声明了android_virtual_devicedimensions是否包含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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 2:58:24

猫抓 cat-catch:5 分钟存下网页视频,m3u8 合并讲透

猫抓 cat-catch&#xff1a;5 分钟存下网页视频&#xff0c;m3u8 合并讲透 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 猫抓&#xff08;cat-ca…

作者头像 李华
网站建设 2026/9/7 2:57:27

电力系统AC功率建模与仿真:潮流计算、暂态分析与故障复现实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:55:16

企业级AI落地卡点与FDE实践:价值驱动破解AI项目失败难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:55:06

普通人本地部署大模型全攻略:Ollama、LM Studio与Dify实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:53:47

多目标跟踪MOT实战指南:从数据关联到工程落地

简介&#xff1a;多目标跟踪资源包聚焦视频监控、自动驾驶、无人机监控等场景下的动态目标检测与跟踪需求&#xff0c;基于VIBE前景检测与卡尔曼滤波的组合方案&#xff0c;先通过像素级背景建模区分前景物体&#xff0c;再利用独立卡尔曼滤波器预测和更新每个目标的位置、速度…

作者头像 李华