使用 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

相关推荐
童话的守望者2 小时前
matrix-breakout-2-morpheus靶机通关
linux·运维·服务器
Frank_refuel2 小时前
Linux操作系统 -> 进程信号(上)
linux·运维·服务器
浮尘笔记2 小时前
从零开始:Android环境搭建与WebView套壳应用
android·前端·android studio·安卓
hnlgzb2 小时前
安卓app中viewmodel的常用的用法有哪些?
android
嵌入式学不会不改名3 小时前
香橙派环境
linux·ubuntu
恋猫de小郭3 小时前
Android Studio Panda 3 发布,CMP 导致的 Gemini 输入问题
android·ide·flutter·ios·android studio
zh_xuan3 小时前
Android compose 可见性动画未执行问题修复
android·compose
BS_Li3 小时前
【Linux网络编程】Socket编程UDP
linux·网络·udp
时光之源3 小时前
程序猿常用命令行(Linux、Windows、Powershell、CMD、conda、pip、apt)
linux·conda·pip