适用范围:android-16.0.0_r2(BUILD_ID BP2A.250605.031.A3)。# Ubuntu 24.04 Android AOSP 16环境搭建
验证环境:Ubuntu 24.04 LTS(x86_64)。
Cuttlefish 简介
Cuttlefish 是 Google 官方的虚拟 Android 设备,基于 Virtio 虚拟化,直接运行真实内核和系统镜像,而不是像模拟器那样做翻译/指令集模拟,因此行为更接近真机。对平台层(内核、Bootloader、HWC、SurfaceFlinger、图形驱动)开发来说,它是比模拟器更可靠的"没有真机也能开发验证"的环境。
几个特点:
- 直接跑编译出来的真实 kernel 和系统镜像,启动后可通过 adb 命令行调试,也可以通过网页 webRTC 看画面;
- 本机编译、本机运行,调试流程是
改代码 → 编译 → launch_cvd 启动 → adb/webRTC 调试,非常适合本地快速验证平台改动; - 也可用于 CI 自动化测试。
0. 环境准备(只需做一次)
确认 server 是否满足要求
检查 CPU、内存和磁盘
nproc
free -h
df -h /home
uname -m
建议:
| 项目 | 建议值 |
|---|---|
| CPU | 8 核以上 |
| 内存 | 32GB 以上;64GB 更好 |
| 磁盘剩余 | 200GB 以上 |
| 架构 | x86_64 |
当前 Android 16 的 Soong bootstrap 对内存要求比较高。即使完整编译使用 -j16,soong_build 在生成 Ninja 构建图时仍可能瞬间使用 20GB 以上内存。
如果 free -h 显示 server 只有 16GB 内存,不建议在这台机器上编译完整 Android 16 Cuttlefish。
检查当前账号是否已经在 kvm、cvdnetwork 组里:
groups
如果输出里没有 kvm 和 cvdnetwork,执行(需要 sudo 密码):
sudo groupadd -f cvdnetwork
sudo usermod -aG kvm,cvdnetwork $USER
加完组之后必须重新登录(重新开一个终端/SSH session)才会生效,同一个老终端里加了组也不会立刻生效。
重新登录后再跑一次 groups 确认已经有 kvm cvdnetwork,才能继续下面的步骤。
1. 编译 aosp_cf_x86_64_phone bp2a userdebug
cd android_16
# 环境
source build/envsetup.sh
# lunch(注意:官方文档写的连字符格式 aosp_cf_x86_64_phone-trunk_staging-userdebug
# 在这个 release 分支上会报错,必须用三个参数分开传)
# release 参数写 bp2a 或 trunk_staging 效果一样:build/release/release_config_map.textproto
# 里 trunk_staging 本身就是 bp2a 的 alias,这里直接写 bp2a 更直观
lunch aosp_cf_x86_64_phone bp2a userdebug
# 内存保护:这台机器 32GB 内存,soong_build 生成构建图时峰值可达 ~28GB,
# 不加这个限制大概率会被系统 OOM Killer 杀掉(现象是编译中途报 "Killed",无其他报错)
export GOMEMLIMIT=16GiB GOGC=80
# 编译(j16 可根据 CPU 核数调整;全量编译约 2 小时)
m -j16
编译完成后产物位置:
- 设备镜像:
out/target/product/vsoc_x86_64/ - 主机工具(launch_cvd 等):
out/host/linux-x86/
编译踩坑自检(如果编译中途失败)
如果 m 编译到 soong_build 阶段直接被 "Killed"(没有其他报错),需要添加内存保护补丁。
用编辑器打开 build/soong/ui/build/soong.go,找到 primaryBuilderInvocation 函数里下面这一段:
allArgs = append(allArgs, commonArgs...)
allArgs = append(allArgs, environmentArgs(pb.config, pb.name)...)
在这两行之后插入:
// Local patch for memory-constrained machines: propagate Go GC memory
// limits into the soong_build child process (it is invoked via `env -i`
// and would otherwise lose these settings, causing OOM kills on hosts
// with limited RAM).
for _, envVarName := range []string{"GOMEMLIMIT", "GOGC"} {
if v := os.Getenv(envVarName); v != "" {
invocationEnv[envVarName] = v
}
}
保存后不需要额外编译这个文件,soong_ui 每次会自动用最新源码重新构建自己。补完之后重新执行 grep -n "GOMEMLIMIT" build/soong/ui/build/soong.go 应该能看到刚插入的这几行,再重新跑 export GOMEMLIMIT=16GiB GOGC=80 + m -j16 即可。
2. 启动 Cuttlefish
每次开新终端都要先执行这一段环境设置(把这几行存成 shell 函数或 alias 会更省事,见文末):
cd android_16
export ANDROID_HOST_OUT=$PWD/out/host/linux-x86
export PATH=$ANDROID_HOST_OUT/bin:$PATH
ulimit -n 65536
ulimit -n 65536 是必须的:默认 SSH 登录的文件描述符上限是 1024,Cuttlefish 主设备(crosvm)要打开的文件描述符数量会超过这个上限,导致设备刚启动就自己崩溃退出(Too many open files),表现为 adb 一直连不上、webRTC 页面一直转圈。
然后启动:
launch_cvd --start_webrtc=true --report_anonymous_usage_stats=n --enable_tap_devices=false --system_image_dir=$PWD/out/target/product/vsoc_x86_64
看到最后一行输出:
VIRTUAL_DEVICE_BOOT_COMPLETED
Virtual device booted successfully
说明启动成功。整个过程大约 20~40 秒。
启动成功后如何使用
方式一:网页 webRTC(推荐,能看到画面)
浏览器打开(同局域网内的电脑都可以访问):
https://<serverIP>:8443
(证书是自签名的,浏览器会提示不安全,选择"继续访问"即可)
点击设备列表里的 cvd-1 进入,点 Connect。如果第一次点击 Connect 之后一直转圈,再点一次 Connect 通常就能连上(这是 ICE 协商到局域网直连路径需要重试一次的正常现象,不是故障)。
方式二:adb 命令行
adb -s 0.0.0.0:6520 shell
常用调试命令:
adb -s 0.0.0.0:6520 root
adb -s 0.0.0.0:6520 shell getprop sys.boot_completed # 输出 1 表示已开机完成
adb -s 0.0.0.0:6520 shell dumpsys SurfaceFlinger
3. 停止 Cuttlefish
cd android_16
export ANDROID_HOST_OUT=$PWD/out/host/linux-x86
export PATH=$ANDROID_HOST_OUT/bin:$PATH
export CUTTLEFISH_CONFIG_FILE=$HOME/cuttlefish/assembly/cuttlefish_config.json
stop_cvd
正常情况下几秒内退出。如果 stop_cvd 报错连不上(比如提示 Unable to connect to launcher monitor),说明配置文件和实际运行的实例对不上(通常是因为又跑了一次没清理干净的 launch_cvd),改用强制清理:
# 找到所有相关进程
pgrep -af "run_cvd|crosvm|launch_cvd|webrtc_operator|netsimd|casimir|wmediumd|secure_env|process_restarter|openwrt_control_server|tcp_connector|tombstone_receiver|modem_simulator|log_tee|socket_vsock_proxy|proxy_adb|proxy_fastboot|adb_connector|kernel_log_monitor"
# 确认上面列出的都是 cuttlefish 相关进程后,一次性杀掉(把 PID 换成上面命令实际输出的号码)
pgrep -af "run_cvd|crosvm|launch_cvd|webrtc_operator|netsimd|casimir|wmediumd|secure_env|process_restarter|openwrt_control_server|tcp_connector|tombstone_receiver|modem_simulator|log_tee|socket_vsock_proxy|proxy_adb|proxy_fastboot|adb_connector|kernel_log_monitor" | grep -v grep | awk '{print $1}' | xargs kill
# 清空实例目录,避免残留配置干扰下次启动
rm -rf $HOME/cuttlefish
4. 重新启动(先停后启)
不要在旧实例还在跑的时候直接再跑一次 launch_cvd——会因为端口冲突(常见报错 Port 6600/9600 Bind failed (Address already in use))启动失败。正确顺序:
# 1. 先检查有没有实例在跑
pgrep -af "bin/run_cvd "
# 2a. 如果有输出(说明有实例在跑),先按第 3 节的方法停止
# 2b. 如果没有输出,直接跳到第 2 节重新启动
5. 常见故障对照表
| 现象 | 原因 | 解法 |
|---|---|---|
lunch aosp_cf_x86_64_phone-trunk_staging-userdebug 报 Missing config 或 Cannot locate config makefile | 这个 release 分支不支持连字符格式 | 改用三参数格式:lunch aosp_cf_x86_64_phone trunk_staging userdebug |
m 编译中途只报 Killed,没有别的报错 | soong_build 内存峰值 ~28GB,被系统 OOM Killer 杀了 | export GOMEMLIMIT=16GiB GOGC=80 后重新 m;确认补丁还在(见第 1 节) |
launch_cvd 报 Permission denied 打开 /dev/kvm,或 Operation not permitted chown config 目录 | 当前 shell 没有 kvm/cvdnetwork 组权限 | 先 groups 确认,没有就按第 0 节加组并重新登录 |
启动后卡在 VIRTUAL_DEVICE_BOOT_PENDING: Bluetooth 一直不动,tombstone 里有 Can't start stack, last instance: starting HciHal | 这台机器上还跑着另一个模拟器/cuttlefish 实例的 netsimd,是全局单例,冲突后新实例的虚拟 Bluetooth 起不来 | pgrep -af netsimd 检查有没有别的模拟器在跑,停掉冲突的那个 |
启动报 Port 6600 Bind failed 或 Port 9600 Bind failed (Address already in use) | 同一台机器上已经有一个 cuttlefish 实例在跑(可能是你自己另一个终端里没关掉的) | 先 pgrep -af "bin/run_cvd " 确认,按第 3 节停止旧实例后再重新启动 |
设备启动几秒后消失,adb 一直 device not found,webRTC 一直 Failed to connect: No such device | 文件描述符(fd)上限太低,主设备 crosvm 因 Too many open files panic 崩溃 | 启动前执行 ulimit -n 65536(见第 2 节,必须步骤) |
| webRTC 网页点 Connect 后一直转圈 | ICE 协商第一次没走通局域网直连路径(正常现象,不是故障) | 再点一次 Connect 通常就好 |
stop_cvd 报 Unable to connect to launcher monitor | 配置文件被新的 assemble_cvd 覆盖,跟实际运行的旧实例对不上了 | 用第 3 节的"强制清理"方法按进程名手动 kill |
6. 可选:把常用步骤存成 shell 函数
把下面这段加到 ~/.bashrc 末尾,以后新开终端只需要输入 cvd_env、cvd_start、cvd_stop 三个命令:
cvd_env() {
cd android_16
export ANDROID_HOST_OUT=$PWD/out/host/linux-x86
export PATH=$ANDROID_HOST_OUT/bin:$PATH
export CUTTLEFISH_CONFIG_FILE=$HOME/cuttlefish/assembly/cuttlefish_config.json
export ANDROID_PRODUCT_OUT=$PWD/out/target/product/vsoc_x86_64
ulimit -n 65536
}
cvd_start() {
cvd_env
launch_cvd --start_webrtc=true --report_anonymous_usage_stats=n --enable_tap_devices=false --system_image_dir=$ANDROID_PRODUCT_OUT
}
cvd_stop() {
cvd_env
stop_cvd
}
加完后执行 source ~/.bashrc 生效一次,以后新终端自动生效。
7. 参考资料
- Cuttlefish 官方文档(中文):source.android.com/docs/device…
- Cuttlefish 快速上手(英文):source.android.com/docs/setup/…
- Android 源码获取与编译:source.android.com/docs/setup/…