引子
座舱里的屏幕越来越多了——仪表、中控、HUD、副驾屏、后排娱乐屏,有些车型还在流媒体后视镜上叠了一层。这些屏幕不是孤立的:导航信息要同时投射到仪表和 HUD 上,空调调节要跨中控屏和后排屏同步状态,语音唤醒后焦点要在多个屏之间跳转。
问题在于,这些屏幕背后跑着不同的进程、不同的 Activity、甚至不同的操作系统实例。多屏交互一旦出问题,用户的感知是"这车怎么这么卡",而测试的难点是——你怎么用自动化去验证这种跨屏、跨进程的协同行为?
在一线汽车大厂的实际项目中,我们把这个问题拆成了三类可自动化验证的场景,用 ADB + dumpsys 的组合方案落地。这篇文章讲清楚思路和踩过的坑。
问题拆解:跨屏测试的三类核心场景
场景一:跨屏数据一致性
导航是最典型的例子。一条导航信息从手机互联或中控地图发起,经过以下链路:
地图应用 → 导航服务 → 投屏协议栈 → 仪表渲染进程 → 仪表屏
↘ → 投屏协议栈 → HUD渲染进程 → HUD屏
中间任何一环的数据丢失、格式转换错误或渲染缓存未刷新,都会导致仪表显示的剩余里程与中控不一致——而这恰好是用户最容易发现的 bug。
自动化要验证的不是"显示对不对",而是同一数据源经不同渲染路径后的输出是否一致。这就需要同时抓取两个屏幕的显示状态做比对——不是 OCR 识图(太慢且不准),而是抓渲染进程的内部数据。
场景二:焦点态与输入源切换
多屏共享同一套输入控制——方向盘按键、语音指令、触控手势。用户按下方向盘上的"确认"键,系统需要判断当前焦点在哪个屏的哪个控件上,然后路由按键事件到正确的进程。
这里涉及跨进程的状态机同步。一旦某个进程的焦点状态没跟上全局,就会出现"按键没反应"、焦点跳乱、或——更隐蔽的——看似响应了但实际上操作的是上一屏的控件。
自动化验证需要做到:模拟外部输入 → 抓取所有窗口的焦点状态 → 断言只有目标窗口获得焦点、其余窗口状态不变。
场景三:投屏延迟量化
从中控投屏到后排屏的延迟不是 ping 一下就能解决的。整个管线是:
投屏指令 → 中控多媒体服务 → 座舱域控 SoC → 显示芯片 → 后排屏点亮
每个环节的延迟都在几十毫秒量级,但串起来就可能超过用户感知阈值(约 150ms)。测试需要端到端地测量这条链路,而不是分别测各段的"理论值"。
方案核心:ADB + dumpsys 的多进程状态抓取
思路很简单:Android 的 dumpsys 命令可以输出所有窗口、Activity、进程的实时状态快照。我们用这个做"测试断言的数据源"——比图像识别准,比埋点轻量。
三层架构
第一层:Window 状态提取
# 抓取当前焦点窗口
adb shell dumpsys window windows | grep "mCurrentFocus"
# 抓取所有可见窗口及其层级
adb shell dumpsys window windows | grep -E "Window\{|mCurrentFocus|isVisible"
一次 dumpsys 输出,能拿到:当前焦点窗口、各 Activity 的生命周期状态(Resumed/Paused/Stopped)、窗口的 Z-order 层级、可见性。这些就是我们做状态快照的全部原材料。
第二层:结构化 Diff 引擎
dumpsys 的原始输出有几百行,两帧之间的差异通常只有几行。我们不需要全量 diff——定义白名单,只看我们关注的关键字段:
WHITELIST_KEYS = [
"mCurrentFocus", # 焦点窗口
"mResumedActivity", # 前台 Activity
"mFocusedApp", # 聚焦的应用进程
"isVisible", # 窗口可见性
"mObscured", # 遮挡状态
"mDisplayId", # 所属屏幕
]
def diff_window_state(before: dict, after: dict) -> list[str]:
"""只比对白名单字段的结构化差异"""
diffs = []
for key in WHITELIST_KEYS:
if before.get(key) != after.get(key):
diffs.append(f"{key}: {before.get(key)} → {after.get(key)}")
return diffs
相比逐帧截图+图像识别,这个方案的时间复杂度 O(k),k 是白名单字段数(通常 < 20),可以跑在高频采样的场景中。
第三层:操作编排
用 Appium/ADB 驱动操作,用 dumpsys 做断言。一条典型的多屏测试用例:
def test_nav_projection_to_cluster():
"""导航从中控投射到仪表:验证焦点切换+数据一致性"""
snapshot_before = dump_window_state()
# 操作:点击"投射到仪表"
adb.tap(540, 2100)
sleep(1.5) # 等待跨进程渲染
snapshot_after = dump_window_state()
diffs = diff_window_state(snapshot_before, snapshot_after)
# 断言1:仪表屏的 Activity 获得焦点
assert any("cluster" in d for d in diffs), \
f"仪表未获得焦点,差异: {diffs}"
# 断言2:HUD 的导航数据与中控一致
cluster_data = extract_nav_data("com.auto.cluster")
hud_data = extract_nav_data("com.auto.hud")
home_data = extract_nav_data("com.auto.home")
assert cluster_data == hud_data == home_data, \
f"跨屏数据不一致: {cluster_data} vs {hud_data} vs {home_data}"
投屏延迟的测量方法
延迟测量的难点是时钟源不统一——不同芯片跑着各自的时钟,直接打时间戳误差大到没法用。
我们的方案是"屏幕差分 + CAN 总线对时":
- 操控端发出投屏指令 → 通过 CAN 总线打统一时间戳 T1
- 接收端每 50ms 轮询截屏 → 逐像素比较前后两帧
- 检测到画面变化 → 用 CAN 时间戳记录 T2
- 端到端延迟 = T2 - T1
实测中,这个方法能做到 ±30ms 的测量精度。也正因为这个精度,我们发现了 WIFI 投屏(~80ms)和蓝牙投屏(~200ms,偶有丢帧)之间的真实差异——在粗粒度测量下这些差异根本看不出来。
两个踩过的坑
坑一:dumpsys 输出不是稳定 API
Google 从不承诺 dumpsys 的输出格式。Android 版本升级、甚至同一个版本的不同 OEM 定制,字段名和输出结构都可能变。我们做了一个适配层——每个 Android 版本维护一套解析规则,在灰度升级前先跑一轮兼容性检查,确保解析逻辑不会因为系统升级而悄悄挂掉。
坑二:不要试图用 dumpsys 做高频轮询
dumpsys 的执行本身有开销(~100-200ms),如果每隔 100ms 就调一次,不仅数据不准——dumpsys 自己就在消耗资源——还会干扰被测进程的正常调度。我们的策略是:只在关键状态切换点(操作前、操作后)打快照,中间过程靠日志做时序分析。
总结
多屏交互的自动化测试,核心挑战不是"能测",而是"测得准"和"测得对"。dumpsys + 结构化 diff 的方案给我们提供了一个低成本、高精度、无需额外埋点的状态验证能力。它不完美——依赖 Android 内部接口、需要版本适配、不适合高频场景——但在当前阶段,它是平衡投入产出比的最佳选择。
#座舱测试 #多屏交互 #自动化测试 #ADB #车机测试
下一篇预告: 座舱自动化测试的稳定性工程——自动化用例"今天绿明天红"的根因分析:环境漂移、竞态条件、图像匹配抖动怎么治。