news 2026/9/19 18:23:05

Ubuntu 24.04编译Android 16 Cuttlefish完整指南:环境配置与踩坑详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ubuntu 24.04编译Android 16 Cuttlefish完整指南:环境配置与踩坑详解

如果你点进来,说明你和我一样,在 Ubuntu 24.04 上折腾 Android 16 的 Cuttlefish 编译。这个需求听起来就是一条命令的事,实际跑起来你才会发现,AOSP 构建系统中的每一个环节都可能成为拦路虎。Cuttlefish 作为 Android 官方力推的虚拟设备方案,在本地编译、系统镜像验证、自动化测试这些场景里几乎是绕不开的。我这次在 Ubuntu 24.04 上从零开始编译 Android 16 的 Cuttlefish,前后花了两天时间,把各种报错都过了一遍,今天把整个过程、决策逻辑和踩坑记录完整梳理出来,希望能给后面的人省点时间。

1. 搞懂 Cuttlefish 在 Android 16 开发里的定位,再动手不迟

很多同学上来就想直接敲make,但 Cuttlefish 和普通的 AVD 模拟器不一样,它更像一套可以在 Linux 主机上运行的"真机硬件抽象层"。谷歌设计它的初衷是替代老旧的 goldfish 模拟器,给 CTS/GTS 测试、兼容性验证、系统级调试提供一个更接近真实设备的虚拟化环境。Android 16 对虚拟化框架的依赖越来越多,用 Cuttlefish 验证要比用 ARM 翻译模拟器靠谱得多,这也是我这次扛着编译风险也坚持用它的原因。

1.1 Cuttlefish 到底是什么"鱼"

Cuttlefish 是一个基于 crosvm 的虚拟化方案,核心思路是把 Android 系统镜像跑在 Linux 的 KVM 之上。它和你在 Android Studio 里常用的 AVD 模拟器有几个关键差异:

  • AVD 模拟器面向应用开发者,提供方便的可视化操作界面,但底层的硬件模拟是做过简化的。
  • Cuttlefish 面向系统工程师和测试工程师,完全命令行驱动,支持快照、多显示器、虚拟摄像头、虚拟传感器,甚至可以通过cvd命令控制设备重启、网络配置等。
  • Cuttlefish 的 HAL 层实现更贴近真机,很多 OEM 在移植 Android 16 时会用它做早期验证。

用一句话概括:AVD 是给你点着玩的,Cuttlefish 是给你跑自动化测试和系统验证的。

1.2 Android 16 为什么要用 Cuttlefish 验证

Android 16 在动态系统更新、虚拟化框架、多用户空间等方面改动不少。如果你只想跑个第三方 App,普通模拟器够用;但如果你想编译完system.img后又刷机、又验证vendor镜像的行为,Cuttlefish 是更快的方式。它可以直接加载完整的 boot、system、vendor 镜像,并且可以模拟 OTA 更新流程。

另外,Cuttlefish 编译产物里包含了 host 端工具和虚拟设备配置,这让它可以很方便地集成到 CI 流水线里。我在本地编译完成后,只需要一条cvd start就能拉起一台虚拟设备,然后用 adb 连接进去做调试,这比反复擦写真机不知道省了多少时间。

1.3 Ubuntu 24.04 作为宿主机的优势与风险

Google 官方构建文档历来对 Ubuntu LTS 版本支持最好,Ubuntu 24.04 的内核升级到了 6.8,KVM 相关模块的稳定性比 22.04 好不少,内存管理和 cgroup v2 的支持也更成熟。但这些"新"也带来了新问题,后面我踩的 libncurses5 缺失、crosvm 兼容性、cgroup v2 权限,都和这个系统版本强相关。

如果你手头有 Ubuntu 22.04,可能会觉得老版本更稳;但如果你为了后续跟 Android 16 的风险,还是建议直接用 24.04。关键是看完这篇文章,把后面这些坑提前避开。

2. 编译前的宿主机环境准备:磁盘、内存和工具链

编译 AOSP 不是简单敲个make就行,宿主机环境准备不充分,编译中途出现各种诡异错误会让你怀疑人生。我把最容易翻车的几个点单独拉出来说。

2.1 磁盘分区与交换空间的底线

Cuttlefish 编译要跑 host 端 crosvm、虚拟设备运行时,还有 AOSP 的镜像产物,这些体积都很大。我这次在一个 500GB 的数据盘上做的编译,最终out目录占了 120GB,如果加上 ccache 和中间缓存,很容易突破 200GB。Ubuntu 24.04 默认安装时根分区通常只给 50GB,如果你只编译一次试试水,可能勉强够;但如果你要反复修改源码、多次编译,磁盘空间一定是第一个瓶颈。

检查磁盘空间可以用:

df -h

如果空间紧张,建议单独挂一个至少 300GB 的分区,或者直接把 AOSP 源码目录和数据放在大分区下。还有一个容易忽略的点是out目录默认在源码树内,如果你不想源码和产物混在一起,也可以用软链接把out指向其他磁盘。

内存方面,16GB 是底线,32GB 会更从容。如果只有 16GB,编译时很可能会被 OOM 杀掉,这时候加一个 8GB 的交换文件能救命:

sudo fallocate -l 8G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile

注意,swap 只是让你不崩,编译速度还是会受影响,所以能加内存就加内存。

2.2 JDK、Python、repo 工具链的版本陷阱

Android 16 的构建系统需要 OpenJDK 17。Ubuntu 24.04 软件源里默认的 OpenJDK 是 21,直接编译会碰到一些 Java 版本相关的错误。我建议先显式安装 OpenJDK 17,并把它设为默认:

sudo apt install openjdk-17-jdk sudo update-alternatives --config java

Python 版本倒还好,Ubuntu 24.04 默认为 Python 3.12,满足 AOSP 要求的 3.8 以上。但你要注意,系统里可能同时存在 python3 与 python,AOSP 的脚本大多调用 python3,所以不要随便把 python 指向 python3。

repo 工具比较关键。Ubuntu 软件源里的 repo 通常比较老,建议用官方脚本安装最新版:

curl -sS https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo chmod a+x ~/bin/repo

~/bin加到 PATH 后,用repo --version验证。如果你在公司网络环境下,可能还需要配置代理,这个自己处理,但别用不安全的工具。

2.3 依赖包安装清单

AOSP 编译依赖的软件包很多,Ubuntu 24.04 上官方文档不一定列全,我把实际用到的整理成一份命令:

sudo apt update sudo apt install -y git-core gnupg flex bison build-essential zip curl zlib1g-dev \ libc6-dev-i386 libx11-dev lib32z1-dev libgl1-mesa-dev \ libxml2-utils xsltproc unzip fontconfig python3 python3-pip \ libncurses-dev libssl-dev libffi-dev

这里有个坑:libncurses5这个老包在 Ubuntu 24.04 的软件源里已经没了,但某些 Android 16 的 host 工具确实还需要它。我在第 6 节会专门讲怎么绕过去,这里先不要急着下载第三方 deb 包。

依赖包安装完成后,可以顺手验证一下 build-essential 是否正常,比如gcc --versionmake --version。有时候你之前装过 Anaconda 或者自己的编译链,导致系统默认的 gcc 版本不对,后面编译很容易出现奇怪的段错误。

3. 用 repo 拉取 Android 16 源码:分支选择与断点续传

源码获取是另一个大坑。Android 16 对应的 AOSP 分支如果没选对,后面编译目标都找不到。我这里的做法是直接用官方 manifest 分支。

3.1 初始化 manifest 分支

创建源码目录并初始化 repo:

mkdir aosp-cf && cd aosp-cf repo init -u https://android.googlesource.com/platform/manifest -b android-16.0.0_r1 repo sync -c -j8

android-16.0.0_r1是 Android 16 的发布标签分支,随着时间推移可能会变成更靠后的r2r3之类。如果你想要最新开发版,也可以直接用main分支,但 main 分支每天都有变化,构建稳定性会差很多。

repo 初始化的时候会提示你设置 git 用户名和邮箱,不然同步代码时会报错:

git config --global user.name "Your Name" git config --global user.email "you@example.com"

3.2 Cuttlefish 在源码树中的位置

同步完成后,你需要确认 Cuttlefish 相关模块是否完整。Cuttlefish 代码主要分布在几个目录:

  • device/google/cuttlefish:核心设备配置与启动脚本
  • external/crosvm:虚拟化底层
  • hardware/google/cuttlefish:HAL 实现和虚拟外设

检查这些目录是否存在:

ls -d device/google/cuttlefish external/crosvm hardware/google/cuttlefish

如果某个目录是空的,很可能是 manifest 分支差异导致的,可以先repo sync再检查一次。如果依然缺失,可能需要重新检查 manifest 是否加载完整。

3.3 网络中断后的断点续拉

AOSP 全量代码差不多 50GB 以上,repo sync 中断是家常便饭。好在 repo 支持断点续传,你只需要再次执行repo sync就能继续。但直接重跑经常会在同一个项目上反复失败,我一般用这样的循环脚本:

for i in {1..10}; do repo sync -c -j4 && break || sleep 5; done

把并发数从 8 降到 4,网络稳定性会好很多。如果你的网络条件较差,还可以用repo sync -c --no-tags跳过 tag 下载,能省不少时间。第一次同步尽量找网络空闲的时段,不然拉一半又断,真的非常磨人。

4. Cuttlefish 编译目标解析:cvd host 包与镜像产物

源码拉完后,真正烧时间的编译环节开始了。Cuttlefish 编译有两个组成部分:Android 系统镜像和 cvd 主机工具。搞懂它们之间的关系,你才知道编译完了该到哪个目录找产物。

4.1 指定编译目标

进入源码根目录后,先设置环境:

source build/envsetup.sh lunch aosp_cf_x86_64_phone-trunk_staging-userdebug

aosp_cf_x86_64_phone是 Cuttlefish 在 x86_64 架构下的手机形态目标,trunk_staging是面向开发阶段的构建变体。Android 16 可能也会提供类似aosp_cf_x86_64_phone-userdebug的目标,你可以用lunch不加参数弹出菜单里查找,或者用:

lunch aosp_cf_x86_64_phone-userdebug

如果你不确定有哪些可用 target,可以执行lunch然后按 Tab 键自动补全,会列出所有 Cuttlefish 相关的项。选userdebug变体是为了保留 adb root 和调试能力,单纯做测试可以选user

之后开始编译:

m -j16

这里-j16的并行数匹配我的 16 线程 CPU。如果你机器线程数不是这个值,用nproc查看一下,然后按第 4.3 节的方法设置合理并行度。

4.2 产物结构

编译完成后,你会在out目录里看到两类关键产物。

第一类是系统镜像,通常位于:

out/target/product/panther/

里面有system.imgvendor.imgboot.img等。注意这里的panther不是高通芯片代号,而是 Cuttlefish x86_64 设备的 product 命名。

第二类是 cvd 主机工具和运行时资源,位于:

out/host/linux-x86/bin/cvd out/host/linux-x86/cuttlefish/

cvd这个二进制就是你用来启动虚拟设备的命令行工具。它依赖同目录下的crosvm和各种配置文件。如果你用 older 版本的 cvd 搭配新编译的镜像,或者反过来,启动时会非常痛苦。我的经验是,每次编译完,优先用同一轮产物里的cvd,避免混合版本。

4.3 并行度与内存控制

编译并行数不是越大越好。AOSP 编译过程中会有许多并行的 ninja 任务,每个任务都会占用一定内存。16 线程 CPU 配 32GB 内存时,-j16基本没问题;但内存只有 16GB 时,-j16很容易 OOM。

我建议按这个经验设置:

  • 32GB 内存 + 16 线程,用-j16
  • 16GB 内存 + 16 线程,用-j8
  • 8GB 内存,不建议尝试完整编译,至少升级到 16GB

另外,m命令还支持 max load,限制系统负载:

m -j16 --max-load=16

这样即使编译过程中你还在干别的,系统也不会被完全卡死。如果你用 ccache,增量编译时并行度可以稍微高一点,因为 ccache 会命中很多重复任务,实际 CPU 压力低一些。

5. 启动 Cuttlefish 前的设备权限与内核虚拟化配置

编译成功只是第一步,真正启动 Cuttlefish 虚拟设备时,你会碰到一堆权限和内核虚拟化相关的问题。很多时候问题不是出在 Android 镜像上,而是宿主机的环境没配好。

5.1 KVM 透传与用户组权限

Cuttlefish 依赖 KVM 做硬件加速。Ubuntu 24.04 默认会加载 KVM 模块,但需要确认你的用户是否有权限访问/dev/kvm

先检查:

ls -l /dev/kvm

如果设备节点存在,但当前用户不在kvm组,cvd start会报Permission denied。解决办法:

sudo usermod -aG kvm $USER newgrp kvm

如果/dev/kvm根本不存在,那就不是权限问题,而是 CPU 虚拟化没开启或者内核模块有问题。

grep -E -c '(vmx|svm)' /proc/cpuinfo sudo modprobe kvm_intel # 或 kvm_amd

如果模块加载失败,可能要检查 BIOS 里的 Intel VT-x / AMD-V 是否开启。这一步不过,后面所有努力都白费。

5.2 udev 规则的作用

Cuttlefish 启动时会创建一系列虚拟设备节点,比如/dev/vhost-vsock/dev/vhost-net/dev/net/tun等。Ubuntu 24.04 的默认权限通常只给 root 或特定组,普通用户直接运行cvd start会遇到大量Failed to open的报错。

官方 deb 包会附带 udev 规则,但源码编译场景需要手动处理。你可以在/etc/udev/rules.d/51-cuttlefish.rules里添加类似这样的规则:

KERNEL=="vhost-vsock", MODE="0660", GROUP="kvm" KERNEL=="vhost-net", MODE="0660", GROUP="kvm" KERNEL=="tun", MODE="0660", GROUP="kvm"

写完后执行:

sudo udevadm control --reload-rules sudo udevadm trigger

这样当前用户只要能访问 kvm 组,就能正常打开这些设备节点。很多人编译成功但启动失败,问题就出在这。

5.3 cvd start 首次启动常见失败

环境配置好之后,启动命令很简单:

cvd start

首次启动常见的报错有这么几类:

  • 内存不足:Cuttlefish 默认会为 Android 分配较多内存,如果宿主机内存小,需要减小配置。
  • /dev/kvm权限:上一节讲过的组权限问题。
  • 无法访问vhost-vsock:检查 udev 规则和crosvm版本。

如果启动失败,别急着重复执行,先看日志:

cvd start --report_anonymous_usage_stats=no ls $HOME/.cuttlefish_runtime/ cat $HOME/.cuttlefish_runtime/cuttlefish.log

日志里通常直接写明了是哪个设备打开失败,定位后对症下药。

6. 实测踩坑记录:从编译报错到启动崩溃的完整排查链路

这一部分是我最想分享的,也是我花时间最多的地方。下面三个问题按我实际排查的顺序记录,希望能帮你省掉几轮瞎折腾。

6.1 libncurses5 缺失引发的 host 工具编译失败

在 Ubuntu 24.04 上编译到某个 host 工具时,我遇到了一个非常经典的问题:

fatal error: ncurses.h: No such file or directory

Ubuntu 24.04 的软件源只提供libncurses-dev(对应 ncurses6),而 Android 16 的某些 host 工具还停留在 ncurses5 时代。直接安装libncurses5-dev会提示找不到包。

我最后用一个稳妥的办法解决了:下载 Ubuntu 22.04 时代的libncurses5-devdeb 包手动安装。注意别乱装 32 位包,装错了反而会把系统库弄乱。下载后执行:

sudo dpkg -i libncurses5-dev_*.deb

如果你的软件源里也找不到这个包,可以从 Ubuntu 的旧版本的 archive 库拉取,但一定要确认架构是 amd64,并且不要覆盖系统已有的libncurses6

另一个折中方案是做一个软链接,把 ncurses5 的头文件链接到新版本上,但这样有时候会引入更隐蔽的 ABI 冲突,我的建议是能下载老包就用老包。

6.2 crosvm 崩溃与 cgroup v2 权限

第一次cvd start时,虚拟设备没有起来,查看cuttlefish.log发现 crosvm 直接崩溃了。日志里有这么一行:

Failed to set cgroup: Permission denied

原因是 Ubuntu 24.04 默认启用了 cgroup v2,而当时我环境里的 crosvm 版本对 v2 的支持并不完整。这个问题的根源在于,Android 16 源码里的 crosvm 可能已经适配了 v2,但 host 端如果装了旧的 crosvm 包,就会优先使用旧版本,导致冲突。

排查方法:

which crosvm crosvm --version

如果发现使用的是/usr/bin/crosvm,而源码编译出来的在out/host/linux-x86/bin/crosvm,你需要把 PATH 环境变量调成优先使用 out 目录下的版本,然后重新启动。

如果确认用的就是新版本还报 cgroup 错误,可以尝试在内核引导参数里加入:

systemd.unified_cgroup_hierarchy=0

切换到 cgroup v1 再启动。但这不是长久之计,最好还是升级 crosvm 到支持 v2 的版本。Android 16 的源码树内版本基本没问题,问题往往出在系统自带的旧版。

6.3 磁盘被 out 目录撑爆

编译一次 Cuttlefish 后,磁盘占用高得惊人。我最初把源码放在 Ubuntu 24.04 的根分区,编译到一半就提示No space left on device

查看后发现out目录就占了 120GB,ccache 也有 80GB,根分区彻底满了。这其实是在准备阶段偷懒了,没有预先规划大分区。

我的解决方案是把 out 目录软链接到大分区:

mv out /data/android/out ln -s /data/android/out out

这样编译产物直接写入数据盘。ccache 目录同理:

export CCACHE_DIR=/data/android/ccache ccache -M 100G

如果你已经编译到一半才发现磁盘不够,可以先把out目录打包放到大分区再软链接回来,但需要确保链接前目录结构正确,不然增量编译会重新生成很多东西。

7. 上手后的优化建议:缓存、参数与日常开发流

环境跑通之后,你会发现 Cuttlefish 其实很顺手,但还有几个优化点能大幅提升开发效率。

7.1 ccache 配置提升增量编译

AOSP 每次编译都会产生大量重复的 C/C++ 编译工作,ccache 能帮你节省非常多时间。第一次编译可能慢,但后续增量编译会快 3 到 5 倍。配置方法:

sudo apt install ccache export USE_CCACHE=1 export CCACHE_EXEC=$(which ccache) ccache -M 100G

这几行建议写进~/.bashrc~/.profile,不然每次新开终端都要重设。注意ccache -M 100G是设置缓存上限,磁盘空间小的话可以设小一点,但至少 50G 才够用。

增量编译时,你会看到类似ccache hit rate 76%的统计。当这个命中率超过 80% 时,你基本就告别长时间等待了。

7.2 自定义 cvd 参数调整设备

Cuttlefish 默认会以 1080p 分辨率、4GB 内存启动。如果你想调整虚拟设备的行为,可以用cvd start带参数。查看所有参数:

cvd start --help

常用的比如设定内存大小或数据分区大小,可以通过运行时配置文件来改。简单粗暴的方式是直接修改~/.cuttlefish_runtime/config.json,但这个文件每次启动会重新生成,最好还是用 cvd 提供的--extra-args或者设置环境变量。我在实际中用的比较多的是关闭图形界面以节省资源:

cvd start --no-graphics

配合 adb 连接后,在 CI 环境里跑测试完全没问题。

7.3 与 Android Studio 协同使用的场景

Cuttlefish 启动后,不需要 Android Studio 也能用 adb 连接,默认端口通常是6520,你需要先通过adb connect 127.0.0.1:6520连接。如果你配置了多个 Cuttlefish 实例,可以通过cvd fleet管理。

为什么说它能和 Android Studio 协同?因为日常写 App 或者做 FrameWork 开发时,你往往需要反复刷镜像、重启设备。Cuttlefish 支持快照(snapshot),可以让你快速回到某个系统状态,比 Android Studio 模拟器自带的快照更稳定。它也能暴露虚拟传感器事件,做自动化测试时非常方便。

我个人在实际操作中的体会是,Cuttlefish 更适合作为"一个人的私有测试云"。白天在公司改代码,晚上回家在同一套源码上继续编译,然后用cvd start拉起的虚拟设备继续调试,整个过程完全不依赖云厂商的 ARM 服务器,x86 电脑上也能跑出完整的 Android 16 系统。把这一套环境理顺之后,你会发现本地编译、本地运行、本地验证这件事,在 Android 系统开发里其实可以做得非常顺滑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 18:20:54

淘宝搜索排名核心机制与实操优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 18:19:59

ANSYS Fluent DPM锥形注入原理与工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 18:17:19

JEB Pro 5.44实战:Android逆向与跨平台动态调试指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 18:14:18

深入解析Linux队列自旋锁:缓存一致性瓶颈与MCS算法实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 18:10:01

蚁群算法优化支持向量机:网络入侵检测的参数寻优实践

简介:针对网络入侵检测中传统误用检测难以识别未知攻击、神经网络又对训练样本要求过高的问题,这份资源提供了一篇基于支持向量机实现入侵检测的研究论文,可作为网络安全与机器学习交叉方向学习者的参考文献和专业指导。论文系统阐述了支持向…

作者头像 李华
网站建设 2026/9/19 18:08:01

区块链如何重构财务共享资金管理:从共识机制到智能合约落地

简介:一份面向财务管理人员、会计信息化研究者及区块链技术应用者的专业参考文献,聚焦财务共享模式下如何借助区块链技术提升资金管理效率。内容系统梳理了区块链的加密算法、共识机制与去中心化特征,分析财务共享的协同性、服务性与技术性&a…

作者头像 李华