一、AOSP组件Mock回放概述:为什么需要Mock回放、核心概念与适用场景

我们先聊聊第一个问题:为什么需要 Mock 回放?

在做 AOSP 测试的时候,你一定遇到过这种情况——某个系统服务(比如 PackageManagerService)的行为非常复杂,一次调用可能涉及数据库、文件系统、底层 Binder 驱动。你想写一个单侧,但发现根本跑不起来。为什么?因为依赖太重了。

说白了,AOSP 的组件之间是高度耦合的。你要测 A,就得把 B、C、D 全都搭好。这是典型的环境依赖问题。我曾经在做一个双卡功能的回归测试时,就因为某个系统属性的状态不对,导致单侧跑了一整天都没通过。后来查了半天,发现是模拟器里的一个 Service 启动顺序乱了。嗯,就在那时候,我开始认真思考 Mock 回放的价值。

Mock 回放要解决的核心矛盾是:“我想测我的逻辑,但我不关心底层基础服务的具体行为”。你想想看,如果你能录一段真实系统里 Service 的响应,然后在测试时直接回放,是不是就可以把这些依赖剥离掉了?

1.1 核心概念拆解

在进入技术细节之前,我们先把这个概念说清楚。Mock 回放不是指简单的对象 Mock,它分为三个阶段:

  1. 录制(Record):在真实设备或模拟器上,拦截组件间的 Binder 调用、Intent 分发、ContentProvider 查询等交互,把请求和响应对记录下来。
  2. 存储(Store):把录制的数据序列化成可复用的文件格式(比如 JSON、Protobuf)。
  3. 回放(Replay):在测试环境中加载录制数据,拦截相同的调用,直接返回记录好的响应。

举个例子。你在测一个 App 的安装流程,其中调用了 PackageInstaller.commitSession()。正常情况下,这个方法会走一个很长的系统调用链。但在 Mock 回放模式下,你可以这样写:

// 伪代码示意
@MockReplay("package_install_commit_session.json")
fun testInstallSession() {
    val sessionId = PackageInstaller.createSession(...)
    // 这里所有对 PackageManagerService 的 Binder 调用
    // 都会被代理层拦截,直接返回录制好的结果
    val result = PackageInstaller.commitSession(sessionId)
    assertTrue(result.isSuccess)
}

你可以看到,测试代码非常干净,完全不依赖真实的系统环境。我个人习惯把这种模式叫做“带上下文快照的 Mock”——它不仅 Mock 了返回值,还 Mock 了调用次序和参数匹配。

1.2 为什么传统 Mock 不够用?

我知道你要问:直接用 Mockito 之类的不行吗?当然行,但要分场景。

对比维度 传统 Mock Mock 回放
依赖管理 需手写每个 Stub 的行为 自动录制真实行为
复杂回调 手写回调逻辑非常痛苦 回调数据自动回放
跨进程调用 Mockito 无法直接 Mock Binder 透明代理,Binder 调用也可拦截
回归成本 API 变更需要手动更新 Mock 逻辑 重新录制即可
测试稳定性 依赖 Mock 实现者的理解是否准确 依赖真实录制数据,更接近生产环境行为

我记得有一次,我们团队用 Mockito 模拟 ContentProvider 的查询,结果写错了返回值格式,导致测试通过但上线后崩溃。改用 Mock 回放后就再没出过这种问题。

1.3 适用场景归纳

不是所有场景都适合用 Mock 回放。我根据自己的实际经验,总结了几个典型场景:

  • 自动化回归测试:每次代码提交后跑一遍,验证核心组件的行为没有退化。Mock 回放可以极大缩短 CI 流水线的耗时,因为不用启动完整的模拟器环境。
  • 本地环境快速验证:你开发了一个新功能,想快速度过“能不能跑起来”的阶段。用 Mock 回放把环境依赖剥离掉,几分钟就能得到反馈。
  • 依赖不稳定或无法访问:比如某些系统 Service 只在特定硬件上存在(如 NFC Service)。使用 Mock 回放,你可以把这些 Service 的调用提前录制好,在普通模拟器上也能测试。
  • 边界条件构造:你可以故意录制一些带有异常数据的回放文件,用来验证自己的代码在边界情况下的处理是否正确。
需要注意:Mock 回放不是万能的。如果你的测试目的在于验证组件间交互的正确性,或者你正在测试的模块与录制时的系统版本差异太大,用 Mock 回放反而可能掩盖问题。我曾经就踩过这个坑——录制的回放文件是基于 Android 12 的,测试时换成了 Android 13 的 API,结果回放数据不匹配,调试了一整天。

1.4 知识体系总览

下面的 SVG 图帮我们把刚才讲的内容串起来。你可以看到整个 Mock 回放的核心逻辑:左边是输入(Mock 规则),中间是核心引擎(代理层与录制/回放引擎),右边是输出(验证结果或行为快照)。这是本章知识体系的结构化表达。

AOSP 组件 Mock 回放核心知识体系 Mock 回放引擎 输入 Mock 规则配置 输入 录制数据文件 输出 正向验证结果 输出 行为快照校验 Binder / Intent 代理层 录制 / 回放调度器 说明 本图展示了 AOSP 组件 Mock 回放的整体流程:从 Mock 规则和录制数据输入, 经过 Binder 代理层和回放引擎的处理,最终输出正向验证结果或行为快照。
个人建议:刚开始接触 Mock 回放的同学,不要试图马上把整个体系都搭建起来。我建议你先从一个简单的 Service 场景入手,比如先录制一个 WindowManagerService 的 addView() 调用,然后写一个测试验证回放是否正常工作。成功一次之后,再扩展到更复杂的多组件交互。

1.5 一个简单的入门示例

为了让你快速理解,我给你看一个最简单但完整的 Mock 回放流程。假设我们要录制一次 BatteryStatsService 的查询,然后在测试中回放:

  1. 录制阶段:在真实设备上启动录制代理,执行一次 getStatistics() 调用,代理层把请求参数和返回结果写入本地文件。
  2. 存储阶段:将录制文件 push 到测试工程目录,比如 test/resources/records/battery_stats.json。
  3. 回放阶段:在测试代码中,用 MockReplay 注解指定回放文件,然后调用相同的 getStatistics() 方法。

你想想看,这个过程如果能跑通,以后你测别的 Service 就只是录制一下的问题了。

核心要点:Mock 回放的关键不是“Mock”本身,而是“回放”的精确性。你必须确保代理层能完整捕获调用链的信息,包括参数类型、顺序、返回类型,甚至是跨进程调用的事务 ID。否则回放时会出现行为不匹配。