Ubuntu 24.04 Android 16 编译 Cuttlefish

0 阅读2分钟

适用范围: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

建议:

项目建议值
CPU8 核以上
内存32GB 以上;64GB 更好
磁盘剩余200GB 以上
架构x86_64

当前 Android 16 的 Soong bootstrap 对内存要求比较高。即使完整编译使用 -j16soong_build 在生成 Ninja 构建图时仍可能瞬间使用 20GB 以上内存。

如果 free -h 显示 server 只有 16GB 内存,不建议在这台机器上编译完整 Android 16 Cuttlefish。

检查当前账号是否已经在 kvmcvdnetwork 组里:

groups

如果输出里没有 kvmcvdnetwork,执行(需要 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

cuttlefish_1.png

cuttlefish_2.png

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-userdebugMissing configCannot 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_cvdPermission 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 failedPort 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_cvdUnable to connect to launcher monitor配置文件被新的 assemble_cvd 覆盖,跟实际运行的旧实例对不上了用第 3 节的"强制清理"方法按进程名手动 kill

6. 可选:把常用步骤存成 shell 函数

把下面这段加到 ~/.bashrc 末尾,以后新开终端只需要输入 cvd_envcvd_startcvd_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. 参考资料