使用 Android Emulator 针对 AOSP 单测编译运行并检查覆盖率的完整实践

📘 AOSP Launcher3 单测覆盖率(.ec)获取完整流程(Android 15 · Headless)

目标:在无 GUI 的服务器环境中,针对 packages/apps/Launcher3
运行单测并稳定生成 coverage .ec 文件


🧠 一、目标与核心路径

最终目标:

text 复制代码
拿到 coverage/ec/launcher3_coverage.ec

完整流程:

text 复制代码
环境准备 → Emulator → 修改 BP → 编译 → 安装 → 运行测试 → 导出 .ec

⚙️ 二、环境准备

1️⃣ Java 环境

bash 复制代码
export JAVA_HOME=~/jdk-17.0.10+7
export PATH=$JAVA_HOME/bin:$PATH

验证:

bash 复制代码
java -version

2️⃣ Android SDK(Headless)

bash 复制代码
mkdir -p ~/android-sdk
cd ~/android-sdk

wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip
unzip commandlinetools-linux-7583922_latest.zip

mkdir -p cmdline-tools/latest
mv cmdline-tools/* cmdline-tools/latest/

环境变量:

bash 复制代码
export ANDROID_HOME=$HOME/android-sdk
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH

验证:

bash 复制代码
adb version
emulator -version

📱 三、Emulator(Android 15)

1️⃣ 安装镜像(推荐 google_apis)

bash 复制代码
sdkmanager \
  "platforms;android-35" \
  "system-images;android-35;google_apis;x86_64"

2️⃣ 创建 AVD

bash 复制代码
echo "no" | avdmanager create avd \
  -n a15_phone \
  -k "system-images;android-35;google_apis;x86_64" \
  --force

3️⃣ 启动(Headless)

bash 复制代码
emulator -avd a15_phone \
  -no-window \
  -no-audio \
  -no-boot-anim \
  -gpu swiftshader_indirect \
  -accel on \
  -cores 4 \
  -snapshot none \
  -no-snapshot-save \
  -memory 4096

4️⃣ 等待系统 ready

bash 复制代码
adb wait-for-device
until adb shell pm path android >/dev/null 2>&1; do sleep 2; done

🔧 四、修改 BP(开启覆盖率能力)

👉 这一步是关键,否则不会生成 .ec


1️⃣ 修改 Launcher3(被测模块)

文件:

text 复制代码
packages/apps/Launcher3/Android.bp

添加:

bp 复制代码
android_app {
    name: "Launcher3",

    static_libs: [
        "jacocoagent",   // 👈 必加
        "Launcher3ResLib",
    ],
}

2️⃣ 修改 Launcher3Tests(测试模块)

文件:

text 复制代码
packages/apps/Launcher3/tests/Android.bp

添加:

bp 复制代码
android_test {
    name: "Launcher3Tests",

    static_libs: [
        "jacocoagent",   // 👈 必加
        "Launcher3TestLib",
        "com_android_launcher3_flags_lib",
    ],
}

🧹 五、清理旧编译产物(避免污染)

bash 复制代码
rm -rf out/target/product/*/testcases/Launcher3Tests
rm -rf out/soong/.intermediates/packages/apps/Launcher3*

🛠️ 六、编译(开启覆盖率)

bash 复制代码
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-ap3a-userdebug

export EMMA_INSTRUMENT=true

m Launcher3 Launcher3Tests -j$(nproc)

📦 七、安装 APK


1️⃣ 先卸载旧版本(避免冲突)

bash 复制代码
adb shell pm uninstall com.android.launcher3.tests || true
adb shell pm uninstall com.android.launcher3 || true

2️⃣ 安装 Launcher3

bash 复制代码
adb push out/target/product/*/system_ext/priv-app/Launcher3/Launcher3.apk /data/local/tmp/
adb shell pm install -r -t /data/local/tmp/Launcher3.apk

3️⃣ 安装 Launcher3Tests

bash 复制代码
adb push out/target/product/*/testcases/Launcher3Tests/*/Launcher3Tests.apk /data/local/tmp/
adb shell pm install -r -t /data/local/tmp/Launcher3Tests.apk

🧪 八、运行单测并生成覆盖率

👉 这里才会真正生成 .ec


运行指定测试类

bash 复制代码
adb shell am instrument --user 0 -w -r \
  -e coverage true \
  -e coverageFile /data/user/0/com.android.launcher3/files/launcher3_coverage.ec \
  -e class com.android.launcher3.model.CacheDataUpdatedTaskTest \
  com.android.launcher3.tests/androidx.test.runner.AndroidJUnitRunner

📂 九、导出覆盖率文件


1️⃣ 创建目录

bash 复制代码
mkdir -p coverage/ec

2️⃣ 从设备导出

bash 复制代码
adb shell run-as com.android.launcher3 \
  cat /data/user/0/com.android.launcher3/files/launcher3_coverage.ec \
  > coverage/ec/launcher3_coverage.ec

3️⃣ 验证

bash 复制代码
ls -lh coverage/ec/launcher3_coverage.ec

📦 十、从 .ec 提取"单文件覆盖率"

👉 前面步骤已经得到:

text 复制代码
coverage/ec/launcher3_coverage.ec

但这个 .ec

text 复制代码
❗ 是"整个模块"的覆盖率,不是单文件

🧠 核心原理

text 复制代码
.ec(全量执行数据)
        +
指定 class 文件
        ↓
Jacoco 过滤
        ↓
得到该 class 的覆盖率

👉 ❗ 本质:

不是生成单文件 ec,而是用 class 从 ec 中"筛出来"


1️⃣ 准备目标 class 文件

👉 从 Launcher3.jar 中解压

bash 复制代码
mkdir -p coverage/tmp/launcher3_classes
cd coverage/tmp/launcher3_classes

jar xf ~/android15/out/soong/.intermediates/packages/apps/Launcher3/Launcher3/android_common/*/jacoco-report-classes/Launcher3.jar

🎯 找到目标 class

例如你要分析:

text 复制代码
CacheDataUpdatedTask.java

对应:

bash 复制代码
coverage/tmp/launcher3_classes/com/android/launcher3/model/CacheDataUpdatedTask.class

2️⃣ 用 Jacoco 生成 XML(只针对单类)

bash 复制代码
java -jar tools/jacoco/jacococli.jar report \
  coverage/ec/launcher3_coverage.ec \
  --classfiles coverage/tmp/launcher3_classes/com/android/launcher3/model/CacheDataUpdatedTask.class \
  --sourcefiles packages/apps/Launcher3/src \
  --xml coverage/tmp/report.xml

3️⃣ 解析 XML(提取分支覆盖率)

核心逻辑:

python 复制代码
counter = root.find(".//counter[@type='BRANCH']")
missed = int(counter.get("missed", 0))
covered = int(counter.get("covered", 0))

percent = covered / (missed + covered) * 100

🔥 最关键结论

text 复制代码
.ec 是全局的
单文件覆盖率是通过 class 过滤得到的

🧠 十一、最终结果

你现在不只是得到:

text 复制代码
coverage/ec/launcher3_coverage.ec

而是可以得到:

text 复制代码
某个 class 的覆盖率(精确到分支级别)

例如:

text 复制代码
CacheDataUpdatedTask.java → 72.5% branch coverage

相关推荐
程序员陆业聪6 小时前
两次Flutter全屏白踩坑复盘:Layout的静默失败,以及AI结对编程的认知盲区
android
夏日听雨眠6 小时前
LInux(逻辑地址与物理地址的区别,文件描述符,lseek函数)
linux·运维·网络
欲儿6 小时前
magicCamera—魔术师的 AR 卡牌应用
opencv·安卓·魔术师
Mackkill6 小时前
Android-纯H5页面项目踩坑记录
安卓
程序员陆业聪7 小时前
Compose Strong Skipping Mode 的真相:它并不会让你的类型变 Stable
android
qq_542515418 小时前
Ubuntu 22.04.4 LTS安装ToDesk最新版打不开,无响应?旧版本4.7.2_277版本分享
linux·ubuntu·todesk
火车叼位8 小时前
替代 Tiny Win10 的 Linux 方案:Debian XFCE 精简桌面搭建
linux·运维
小麦嵌入式8 小时前
FPGA入门(四):时序逻辑计数器原理与 LED 闪烁实现
linux·驱动开发·stm32·嵌入式硬件·fpga开发·硬件工程·dsp开发
皮卡蛋炒饭.9 小时前
传输层协议UDP
linux·网络协议·udp
syagain_zsx9 小时前
Linux指令初识(实用篇)
linux·运维·服务器