Ubuntu 24.04上编译Android 16 Cuttlefish完整指南 最近在 Ubuntu 24.04 上把 Android 16 的 Cuttlefish 完整编译跑通了从裸机环境准备到最终cvd start启动虚拟设备整个过程踩了不少坑也顺手解决了好几个之前一直没弄明白的编译细节。Cuttlefish 是 Google 官方的虚拟 Android 设备方案AOSP 的很多 CI 用例、CTS/VTS 测试都是基于它跑的对于做系统开发、框架层调试或者想低成本验证多设备方案的人来说比买一堆真机要省事得多。这篇文章就把我在 Ubuntu 24.04 上编译 Android 16 Cuttlefish 的完整过程整理出来包含环境要求、依赖安装、repo 同步、编译参数选择、启动验证以及我实际遇到的各种报错和排查思路希望能帮你少走点弯路。1. 项目整体思路为什么要编译 Cuttlefish以及编译前的核心判断1.1 Cuttlefish 到底是什么解决了什么问题Cuttlefish 是 Google 在 AOSP 里维护的一套虚拟 Android 设备实现底层基于 QEMU 和 crosvm 的混合方案它把 Android 系统跑在一个虚拟化环境中宿主机通过 KVM 提供硬件加速。和传统的 Android Emulator 相比Cuttlefish 最大的优势在于它和 AOSP 主线紧密结合几乎每个 AOSP 版本发布时都会同步更新 Cuttlefish 的配置和驱动所以它是调试原生系统镜像、验证 HAL 层改动、跑 CTS/GTS 测试的首选环境。简单说Cuttlefish 可以理解成一个可以跑的 AOSP 镜像验证器。你编译完 AOSP 之后与其刷到真机上慢慢等启动日志不如直接启动一个 Cuttlefish 实例用 adb 连进去看dmesg、抓logcat、跑cts效率会高很多。实际开发中我经常用它来做 Soong 编译规则验证和 SELinux 策略验证改完product配置或者sepolicy几分钟内就能起一个实例测试比连接实体设备方便太多了。1.2 为什么选择 Ubuntu 24.04 Android 16 的组合Android 16 对应的 AOSP 版本对构建环境有明确要求推荐使用较新的 Linux 发行版而 Ubuntu 24.04 LTS 是目前很稳妥的选择。一方面 24.04 的 GCC、Python3、OpenJDK 版本都在 AOSP 的兼容范围内另一方面作为 LTS 版本后续更新和依赖维护也更省心。我一开始在 Ubuntu 22.04 上试过部分系统库版本偏旧需要额外处理一些兼容问题后来直接换到 24.04 干净环境省掉了不少麻烦。Android 16 的系统镜像和 VTS 测试套件对虚拟化支持要求更高Cuttlefish 在 Android 16 里默认使用 crosvm 作为主虚拟化方案对内核特性和 KVM 的版本有要求。Ubuntu 24.04 默认内核是 6.8KVM 模块很完善直接支持嵌套虚拟化和大页内存这些特性对 Cuttlefish 的性能影响非常大实测下来比之前的组合要稳定不少。1.3 整体实施路径从环境准备到实例启动需要经历的阶段整个项目大致分为四个阶段环境准备、源码同步、编译构建、启动验证。环境准备阶段重点是确认 CPU 虚拟化支持、安装依赖库、配置 repo 工具源码同步阶段需要选择一个合适的 AOSP 分支通过repo init和repo sync拉取代码编译构建阶段核心是初始化build/envsetup.sh、执行lunch选择目标设备配置然后m编译出完整的 Cuttlefish 镜像和工具链启动验证阶段则是用编译产出的cvd工具启动虚拟设备确认系统能正常 boot 到 launcher并且 adb 能连上。这四步看起来简单但每一步都可能出幺蛾子。比如 repo 同步一半中断、磁盘空间不足、KVM 设备没有权限、lunch 选错 target 导致编译产物不完整这些我都踩过。后面几节我会按这个路径一步步拆开讲把每一步背后的原理和注意事项都说明白这样即使你换了一个 Linux 发行版或者换了一台服务器也能举一反三处理问题。2. 环境准备硬件要求、系统配置与依赖安装2.1 硬件配置要求CPU、内存、磁盘的最低门槛和推荐配置编译 AOSP 全量代码对硬件的要求不能只看官方文档上的最低配置官方文档说 16GB 内存、400GB 磁盘可用但实际跑起来如果你同时开编译和 Cuttlefish 实例16GB 基本是作死。我个人的建议是 32GB 内存起步64GB 会更从容尤其是编译 Cuttlefish 这种需要同时启动多个虚拟设备实例的场景内存不够会直接 OOM 或者把 swap 打满编译速度直线下降。CPU 方面核心数越多越好编译的时候-j参数会直接吃满所有核心我自己用的 16 核 32 线程的机器第一次全量编译大概花了 1 小时 40 分钟如果是 8 核的机器可能要奔着 4 小时去了。磁盘空间这个坑很多新手会踩repo sync 完成后源码加 .git 目录差不多 200GB 左右编译产物再加 150GB 左右加上可能需要的 ccache 缓存我建议至少留出 600GB 到 800GB 的空闲空间而且最好用独立分区避免和系统盘混在一起。2.2 确认虚拟化支持KVM 是否可用是 Cuttlefish 能不能启动的前提Cuttlefish 在本地运行依赖 KVMKernel-based Virtual Machine也就是 Linux 内核级的硬件虚拟化支持。编译完镜像之后如果宿主机没有 KVM 或者当前用户没有/dev/kvm的访问权限虚拟设备是起不来的。所以在正式编译之前我强烈建议先验证一下 KVM 是否可用这一步能省掉后面排查问题的大量时间。验证方法很简单执行ls -l /dev/kvm如果能看到这个设备节点说明内核模块已经加载了。如果看不到先检查 BIOS 里 Intel VT-x 或 AMD-V 有没有开启然后再确认kvm_amd或kvm_intel模块有没有加载可以用lsmod | grep kvm查看。Ubuntu 24.04 默认桌面版和服务器版都会自动加载 KVM 模块但如果用的是虚拟机里套虚拟机比如在 VMware 里跑 Ubuntu就需要开启嵌套虚拟化这一步很多云服务器是默认关闭的需要跟服务商确认。确认/dev/kvm存在之后还要看当前用户有没有读写权限默认情况下设备属于kvm组所以把当前用户加入kvm组是必须的sudo usermod -aG kvm $USER然后重新登录或者newgrp kvm让权限生效。2.3 依赖库安装清单Ubuntu 24.04 下需要装哪些包AOSP 编译依赖很多系统库缺少任何一个都可能导致编译中断或者最终产出的镜像有问题。Android 16 的系统要求比之前版本更苛刻除了常规的git、python3、curl之外还需要openjdk-17-jdk作为默认 JDK因为 Android 16 的构建系统已经全面切换到 Java 17。openjdk-11在编译高版本 Android 时会报错千万别装错了。我整理了一份 Ubuntu 24.04 下编译 Android 16 的依赖安装清单直接执行即可sudo apt update sudo apt install -y git-core gnupg flex bison build-essential zip curl zlib1g-dev \ libc6-dev-i386 libncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev \ libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig \ python3 python3-pip python3-venv openjdk-17-jdk \ qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils \ libssl-dev libgflags-dev libprotobuf-dev protobuf-compiler \ libevent-dev libgtest-dev libjsoncpp-dev \ libunwind-dev liblz4-tool libnss3-dev libatk-bridge2.0-dev \ libgtk-3-dev libgbm-dev libdrm-dev libasound2-dev \ ccache这里面有几个值得解释一下。libncurses5-dev和lib32z1-dev是给 32 位宿主工具用的AOSP 构建系统里有些旧的 host 工具还是 32 位的缺少这些库会直接链接失败。ccache是编译缓存工具强烈建议装上尤其在多次增量编译或者切换分支的时候ccache 能把编译时间缩短一半以上。注意不要偷懒跳过qemu-kvm和libvirt-daemon-systemCuttlefish 的虚拟化层虽然主要用 crosvm但它的配置脚本和网络管理工具会依赖 libvirt 的组件缺了会有莫名奇妙的启动错误。2.4 配置 repo 工具和环境变量repo 是 Google 为 Android 多仓库管理专门写的 Python 脚本它不是 apt 包需要手动下载并放到可执行路径下。常规做法是下载 repo 脚本到用户目录下的~/bin文件夹然后配置 PATH 和 git 全局参数。mkdir -p ~/bin curl -sS https://storage.googleapis.com/git-repo-downloads/repo ~/bin/repo chmod ax ~/bin/repo echo export PATH$HOME/bin:$PATH ~/.bashrc echo export USE_CCACHE1 ~/.bashrc echo export CCACHE_EXEC/usr/bin/ccache ~/.bashrc source ~/.bashrcgit 配置也需要注意AOSP 仓库的 URL 和 commit-id 校验依赖 git 的 user 信息虽然没有强制要求但不配置的话某些 repo 命令会提示警告。执行下面的命令设置一个全局的 user.name 和 user.email用你自己的名字和邮箱即可。git config --global user.name Your Name git config --global user.email your.emailexample.com git config --global color.ui false3. 源码同步分支选择与 repo 初始化3.1 分支选择trunk staging 和 release 分支怎么选AOSP 代码仓库的分支非常多对于 Android 16 的 Cuttlefish 编译主流选择是android16-release分支或者main分支也就是 trunk。两者的区别在于release 分支相对稳定代码经过了比较充分的测试适合希望复现问题、进行稳定版本开发的场景trunk 分支则是日常开发主线包含了最新的功能和变更但偶尔会遇到某天编译不过或者某个 Cuttlefish 配置临时坏掉的情况。我个人建议如果你是想稳定复现问题或者给一个正式的 Android 16 项目做准备选android16-release分支如果你是想跟进 Cuttlefish 的最新特性做移植适配或者你本来就是 AOSP 贡献者那就用main分支。分支名可以在官方 manifest 仓库的 branch 列表里查到repo init 的时候通过-b参数指定。3.2 repo init 和 repo sync 实操选好分支之后就进入代码拉取环节。先建一个工作目录然后在目录里执行repo init指定 manifest 仓库和分支。mkdir -p ~/aosp cd ~/aosp repo init -u https://android.googlesource.com/platform/manifest -b android16-release如果repo init执行的时候卡住或者报 SSL 证书错误可能是网络环境对 google 域名不友好需要配置镜像源。国内开发者在拉 AOSP 时一般都会选择使用镜像同步具体怎么配置镜像有很多现成的方案这里不展开。我实际处理过一次 SSL 报错最终的解决办法是使用镜像地址重新 init之后就通畅了。repo init完成之后工作目录下会生成一个.repo隐藏目录里面保存了 manifest 仓库的信息。接下来执行repo sync拉取全部分支代码repo sync -j32 -c --no-tags-j32表示并行下载的任务数可以根据你的带宽和 CPU 情况调整我建议至少-j16。-c是只同步当前分支的最新代码不拉取历史 tag能节省大量空间和时间。--no-tags表示不同步 git tag同样是为了减少数据量。第一次 sync 的时间取决于带宽一般 2 到 4 小时如果中途断了再跑一次repo sync会从断点继续结合-j高并发参数补数据的速度比首次同步还要快。3.3 源码同步完成后的目录结构检查sync 完成以后先别急着编译花几分钟检查一下目录结构是否完整。Lunch 和编译阶段需要用到build/make和build/soong这两个核心目录如果它们缺失说明 manifest 初始化不完整需要重新确认分支和repo sync状态。检查方法很简单cd ~/aosp ls build/ source build/envsetup.sh如果envsetup.sh能正常加载不报错说明构建系统脚本都在。另外检查vendor/google/cuttlefish目录是否存在Cuttlefish 的 host 工具包和虚拟设备配置都在这里Android 16 之后部分模块移动过位置如果目录不存在大概率是 repo init 的时候选了精简 manifest 导致模块未包含需要在repo init加上额外参数或者检查default.xml是否包含了受限模块。4. 编译实操lunch 目标选择、编译命令与产物解析4.1 选择合适的 lunch target为什么要用 aosp_cf_x86_64_phoneCuttlefish 虽然有多个设备形态比如aosp_cf_x86_64_phone、aosp_cf_x86_64_foldable、aosp_cf_x86_64_tablet但通用性最强、踩坑最少的是phone类型。它对应的是一个标准的直板手机屏幕配置各种 CTS 测试默认跑的也是这个形态。如果你的目标是验证折叠屏逻辑或者大屏适配再换 foldable 和 tablet 的 target但首次编译务必先用 phone 打通全链路。还有一个常见的配置后缀trunk_staging和userdebug的区别。lunch aosp_cf_x86_64_phone-trunk_staging-userdebug表示使用 trunk staging 的设备配置userdebug表示这是一个可调试的 user 版本既保留了 adb root 权限又接近真实用户环境的性能表现。对于日常开发和问题定位userdebug是默认选择eng版本权限更大但性能更差user版本没有 root 权限一般不用于 Cuttlefish 调试。也有人在编译 Cuttlefish 时喜欢直接lunch aosp_cf_x86_64_phone-userdebug这个配置同样可以编出可用的 Cuttlefish 镜像区别在于 trunk_staging 会带上一些 Android 16 的新特性配置比如更完整的 Cuttlefish 服务端模块。我的经验是如果有对应的 trunk_staging target优先用它因为 AOSP 的 Cuttlefish 代码更新更频繁地在 trunk staging 里验证过。4.2 初始化构建环境envsetup.sh 和 lunch 的执行细节执行编译之前需要先初始化环境脚本然后选择目标设备。全过程如下cd ~/aosp source build/envsetup.sh lunch aosp_cf_x86_64_phone-trunk_staging-userdebuglunch执行成功后会输出大量环境变量包括 TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_ARCH 等等。这里要注意如果你在同一终端里切换过多个 target最好重新打开一个终端再执行避免旧的环境变量干扰新的构建。我自己遇到过因为忘记重新 source 导致编译产物的架构还是上一个 target 的白白浪费一小时。lunch之后Cuttlefish 相关的模块是否默认包含取决于产品配置文件。Android 16 中aosp_cf_x86_64_phone.mk里默认加入了include $(CUTTLEFISH_COMMON) ...所以你不需要手动去改 product 文件加模块官方配置已经包含了 Cuttlefish 的 host 工具和虚拟设备镜像。如果后续你想自定义 Cuttlefish 的网络配置或者硬件配置可以仿照默认配置文件修改但那是进阶话题本文先讲默认编译。4.3 编译命令详解m、m dist、m 参数怎么组合构建环境的初始化完成之后就可以正式编译了。最直接的命令是m -j$(nproc)-j$(nproc)是自动获取 CPU 线程数并全部用于编译如果你还开着其他任务建议手动限制一下比如m -j16。全量编译 AOSP 加 Cuttlefish 镜像在 16 核 32 线程 64GB 内存的机器上大概 1.5 到 2 小时如果内存只有 16GB强烈建议先加export ANDROID_JACK_VM_ARGS-Xmx4g这类参数限制 JVM 堆内存否则很容易 OOM。另一条常用命令是m dist它会在编译完成后生成一个可发布的产物包里面包含cvd-host_package.tar.xz和各类系统镜像。dist包的好处是方便拷贝到其他机器上直接启动 Cuttlefish不用把整个 out 目录都带走。执行方式m dist -j$(nproc)4.4 编译产物完整清单哪些文件是 Cuttlefish 的最终产物编译完成以后out/target/product/和out/host/linux-x86/下有大量产物但最核心的 Cuttlefish 相关文件主要集中在两个位置。产物路径作用out/host/linux-x86/bin/cvdCuttlefish 主管理工具负责创建、启停虚拟设备实例out/host/linux-x86/bin/launch_cvd旧的启动命令cvd start 内部也会调用它out/host/linux-x86/cuttlefish/host 工具链和依赖库的集合目录out/target/product/panther/模拟 Pixel 设备的产品目录含 system.img、vendor.img 等out/target/product/panther/*.imgCuttlefish 使用的系统镜像out/soong/host/linux-x86/bin/cvd部分版本的 cvd 实际编译产物会同步到这里需要提醒的是Android 16 的 Cuttlefish 设备名从之前的vsoc_x86_64改成了panther所以你在out/target/product/下看到的不再是vsoc_x86_64目录而是panther这是正常的。如果列目录看不到panther检查一下 lunch target 是不是选成了真机设备比如aosp_crosshatch那个是给 Pixel 3 编译的不会生成 Cuttlefish 需要的 vhost 配置。4.5 增量编译技巧ccache 配置和 soong 增量编译的正确方式Cuttlefish 系统开发通常不会只编一次改动 HAL 层代码或者内核模块都会涉及重新编译。AOSP 的增量编译在 Soong 系统下已经比较完善理论上只需重新执行m命令Soong 会分析变更的模块并重建依赖部分。但如果你不配置 ccache每次全量编译的时间成本依然很高。配置方法很简单在lunch之前设置环境变量export USE_CCACHE1 export CCACHE_EXEC/usr/bin/ccache ccache -M 100Gccache -M 100G设置编译缓存的上限如果磁盘空间充足我建议设置为 200G官方推荐的是 50G 到 100G。设置之后第一次编译会缓存所有 C 和 C 的编译结果后续清理输出目录重新编译时那些没有变化的模块会直接从缓存读取速度能快 3 到 5 倍。更换分支后 ccache 依然有效因为缓存 key 是基于编译命令行和源文件哈希生成的只要文件内容不变就能命中。另一个实用技巧是只编译 Cuttlefish 需要的模块而不是全量构建。如果你只是改了vendor/google/cuttlefish下的代码可以用模块名直接编译m -j$(nproc) cvd m -j$(nproc) libcuttlefish5. 启动验证cvd 启动 Cuttlefish 实例的完整流程5.1 启动前的检查KVM 权限、端口冲突、cvd 路径配置编译完成之后进入验证阶段。Cuttlefish 的启动命令是cvd start但直接执行往往会各种报错首先检查环境变量和工具路径是否正常。ls -l /dev/kvm确认/dev/kvm存在且当前用户有 rw 权限。然后检查cvd命令是否能被找到如果out/host/linux-x86/bin不在 PATH 中需要手动把它加进去export PATH$PATH:~/aosp/out/host/linux-x86/bin接着检查当前机器有没有其他虚拟化进程占用了端口或/dev/kvm。Cuttlefish 默认会占用 6520 到 6522 等端口段如果之前有残留的实例没有正常关闭可能会导致启动失败。可以用cvd reset清理所有残留实例也可以手动查看ps aux | grep cvd并 kill 掉相关进程。5.2 cvd start 的第一次启动日志输出和实例状态理解一切就绪后执行启动命令cvd start首次启动时cvd 会读取out/target/product/panther下的系统镜像然后创建一个完整的虚拟机实例并用宿主机 KVM 加速启动。正常启动过程大概需要几分钟期间终端会输出大量日志包括内核启动信息、Android init 阶段日志以及 Cuttlefish 自身的服务状态。启动完成后可以用cvd fleet查看虚拟设备列表用cvd instances查看每个实例的详细信息。如果启动失败日志会直接打印FATAL或者WARNING级别的错误常见的有资源不足、镜像缺失、KVM 权限不足等按日志信息去排查一般很快就能定位。5.3 adb 连接验证确认 Android 系统真正跑起来了Cuttlefish 实例启动之后通过 adb 连接验证系统是否真正 boot 完成。首先看 adb 是否能发现设备adb devices正常情况下会看到一个localhost:6520或者127.0.0.1:6520的 adb 设备。如果 adb 列表为空可能是 adb server 版本过旧或者没有识别到 Cuttlefish 的 adb 端口可以试试adb kill-server adb start-server重新启动 adb server。注意 Cuttlefish 的 adb 端口不是标准的 5555而是 6520 起的一段端口所以 adb 需要能够自动发现 mdns 服务或者已经通过环境变量配置了端口。连接上 adb 之后执行adb wait-for-device等待设备完全启动然后用adb shell getprop sys.boot_completed检查启动状态返回 1 就代表 Android 已经完全起来了。如果只是adb shell能进去但sys.boot_completed还是 0说明系统还在执行zygote启动和system_server初始化再等一会儿就好。5.4 可视化验证通过 Cuttlefish 的 Web UI 和 VNC 观察界面Cuttlefish 本身是 headless 的虚拟设备默认情况下没有图形界面但它的 Web UI 会在启动时内置在某些配置里。你可以通过浏览器访问 Cuttlefish 提供的 Web 控制台来观察设备界面不过这个需要提前在编译时开启相关组件如果没开启也可以通过cvd start -v nc这种模式启动带 VNC 支持的实例然后用 VNC 客户端连上去看画面。更实用的验证方式是用scrcpy或者 adb screencap 截图来确认 UI 状态配合 adb shell 指令做交互测试。因为 Cuttlefish 本质上是跑在真机虚拟化上的完整 Android所以 UI 渲染是完整的只是没有物理屏幕通过截图就能很清楚地看到 launcher 是否正常加载、状态栏时间是否更新、四周边框是否显示虚拟按键。这些小细节能帮你判断图形栈有没有出问题。5.5 停止实例和清理环境开发过程中频繁启停 Cuttlefish 实例是家常便饭。推荐使用cvd stop优雅停机它会先关闭 Android 系统再回收虚拟机资源。如果实例卡死用cvd reset强制清理所有资源。多个实例同时跑的时候尽量用cvd stop -n id指定实例编号避免把其他正在运行的实例误杀。如果当前用户对/dev/kvm没有写权限启动时还会遇到一个很典型的报错libvirt: QEMU error: Failed to connect socket...。这是因为 Cuttlefish 启动时需要通过 libvirt 管理虚拟化资源。解决办法就是把用户加入kvm和libvirt组然后重新登录或者干脆用 root 用户跑cvd start不推荐但确实能解燃眉之急。6. 常见问题与排查技巧实录6.1 repo sync 中断或下载卡顿的排查repo sync是很多新手最容易卡住的环节表现是拉取到某个仓库时长时间不更新或者直接报连接超时。这种问题大部分不是代码问题而是网络问题。我的第一建议是提高并行度和关闭 git 压缩repo sync -j32 -c --no-tags这样能最大限度地利用带宽。第二建议是养成断点续传的习惯repo sync本身是幂等的中断之后重新执行即可不用担心损坏。如果某个仓库永久性卡住可以试试删掉这个仓库的.git/index.lock文件再同步这个锁文件偶尔会因为进程被杀而残留导致后续同步无法继续。6.2 编译过程中 OOM 和磁盘空间不足AOSP 编译对内存和磁盘的消耗非常惊人尤其当你执行m -j$(nproc)时32 线程同时编译内存峰值能到 40GB 以上。如果你的机器内存只有 16GB建议加上这个参数来给每个编译进程限制内存export ANDROID_COMPILE_WITHOUT_SYSTEM_CORE1这个参数的作用是跳过系统核心模块的编译对 Cuttlefish 这种虚拟设备镜像也有一定作用但有时会影响产物完整性建议谨慎使用。更稳妥的方案是添加 swap 空间Ubuntu 24.04 默认不配置 swap 文件你可以手动创建一个sudo fallocate -l 32G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile磁盘空间不足的典型现象是编译器直接报No space left on device然后整个构建过程崩溃。提前用df -h检查分区剩余空间如果 out 分区和源码头目录空间不足可以考虑把 out 目录挂载到独立的大分区上或者通过软链接把out移到大磁盘目录。6.3 lunch 阶段报错MISSING PRODUCT 和找不到 build 配置lunch阶段最常见的报错是Couldnt locate the directory for product aosp_cf_x86_64_phone。这个报错的原因通常是产品配置文件没有包含在 repo sync 的清单里。Android 16 的 Cuttlefish 产品配置在device/google/cuttlefish目录下如果这个目录没有被同步下来lunch自然找不到。对策是回到repo init阶段确认使用的是完整版 manifest或者手动拉取缺失仓库。还有一种情况是你的 repo 分支比较老产品配置目录结构变了。比如某些分支里 Cuttlefish 配置从device/google/cuttlefish挪到了vendor/google/cuttlefish而你的本地 manifest 没有跟着更新导致lunch找不到目标。解决办法就是同步最新 manifestrepo sync或者切换到一个更确定的分支。6.4 Cuttlefish 启动失败crosvm 报错、KVM 资源不可用Cuttlefish 启动失败的报错有很多种我整理了一下最典型的几个如下表报错信息原因排查思路Failed to create crosvm instancecrosvm 无法创建虚拟机检查/dev/kvm是否存在及权限Could not open /dev/kvm当前用户无 kvm 权限执行sudo usermod -aG kvm $USER并重新登录The instance has been stoppedcvd 启动过程中系统崩溃查看~/.cuttlefish下日志文件分析崩溃原因Failed to connect to socket网络或 libvirt 服务异常确认 libvirtd 服务是否在运行systemctl status libvirtdPort 6520 already in use端口被残留实例占用执行cvd reset清理后重试有一类容易忽略的问题是宿主机的 cgroup 或 namespace 限制。如果你在 Docker 容器里跑 Cuttlefish容器默认对设备节点和控制组有严格限制/dev/kvm也可能映射不进去所以我个人很不建议在 Docker 里直接跑 AOSP 编译和 Cuttlefish除非你已经做好完整的设备透传和特权模式配置。6.5 系统镜像缺少 ramdisk 或 recovery 相关文件编译产物缺失文件的情况虽然不常见但一旦出现排查起来会有点绕。比如 Cuttlefish 镜像压缩包里的 ramdisk.img 为 0 字节或者 recovery.img 缺失启动时直接报找不到镜像。原因多半是你在lunch切到 Cuttlefish target 之前旧 target 的临时文件还没清干净导致 Soong 复用了不匹配的中间产物。我的习惯是切换 target 后先清理变量再编译source build/envsetup.sh lunch aosp_cf_x86_64_phone-trunk_staging-userdebug make clobber m -j$(nproc)make clobber会清空 out 目录里的所有编译产物虽然慢了点但能保证绝对干净。如果m编译过程中某个模块失败一般是代码同步不完整导致的可以重新执行repo sync再编译。7. 实际体验与项目应用建议7.1 Cuttlefish 相比真机调试的核心优势我把 Cuttlefish 用于日常开发和测试已经有快一年时间了最大的感受是环境复用带来的效率提升。真机调试时每次改内核或者 HAL 都要重新刷机一套流程走下来十几分钟就没了而且设备多了还有线缆、充电、散热这些物理问题。Cuttlefish 是纯软件方案起停实例只需要几秒钟配合cvd的多实例管理能力我可以在同一台服务器上同时起多个不同 Android 版本或者不同屏幕形态的虚拟设备跑并发测试的效率非常高。另外Cuttlefish 的日志系统比真机更完善虚拟设备的串口日志、内核日志、Android logcat 都能通过 host 端统一输出定位问题的时候可以同时开多个终端跟踪不同层级的日志这对系统工程师来说太友好了。7.2 什么时候还是需要真机Cuttlefish 也有它的局限性如果你要调试的内容涉及真实传感器、蜂窝信号、GPS、蓝牙硬件抽象层虚拟设备的模拟实现和真机差距还是很大的这时候虚拟设备只能做功能验证最终还是要回到真机。另外如果你主要做的是相机 pipeline 优化、功耗调优这类对真实硬件依赖很强的项目也不要指望 Cuttlefish 能完全替代真机。但在绝大多数 framework 层开发、系统应用开发、兼容性测试、多设备交互场景Cuttlefish 已经能覆盖很大一部分需求而且它的成本比真机集群低得多非常适合个人开发者和中小团队。7.3 从单实例到多实例Cuttlefish 的进一步玩法编译和启动一个 Cuttlefish 实例只是入门掌握了基本流程之后可以进一步玩转多实例管理。cvd start默认只会启动一个实例但你也可以通过参数指定实例数量比如cvd start --num-instances4这样会同时启动 4 个相互独立的虚拟设备。每个实例都会分配独立的 adb 端口和 Web UI 端口你可以分别为它们安装不同的 APK、推送不同的配置文件非常适合做多设备协同测试。多实例还有一个用途是模拟设备农场配合cvd reset和 dashboards 工具可以搭建小型的 AOSP 测试集群跑大规模并行测试。虽然这类玩法对服务器配置要求较高但思路是相通的理解了单实例的启动机制多实例管理自然就顺手了。8. 项目复盘与避坑心得这篇文章写下来其实也是我自己完整复盘了一遍在 Ubuntu 24.04 上编译 Android 16 Cuttlefish 的过程。回想最开始踩过的坑有两点印象最深。第一是环境变量和 target 的选择要耐心核对很多莫名其妙的编译错误其实都是lunch没有重新执行、或者 ccache 配置残留导致的不要一上来就认定是代码问题先排除环境因素。第二是repo sync阶段要留足时间首次拉代码是最耗时的环节而且网络抖动导致的失败很难完全避免多尝试几次、用高并发参数是能明显降低失败概率的。我在实际操作中还有一个心得是每当编译遇到问题时优先看日志而不是盲目重启构建。AOSP 的日志系统已经足够友好把终端输出导出到文件然后grep error往往能直接定位问题。别急着加参数重跑先搞清楚失败原因不然很可能在同一个地方反复栽跟头。最后再分享一个小技巧如果你只是想体验 Cuttlefish 的启动和 adb 调试流程不一定要从源码完整编译。AOSP 官方会不定期发布编译好的 Cuttlefish host 包和镜像下载下来直接配合 KVM 环境就能跑起来。当然做系统开发或者二次开发就得老老实实走完整编译流程这条路虽然慢但真正遇到问题时给你带来的理解和掌控感是直接下载镜像得不到的。希望这篇记录能帮你顺顺利利把 Cuttlefish 跑起来少走一些我当年走过的弯路。