ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android 16 AOSP 编译报错排查与解决实战指南

Android 16 AOSP 编译报错排查与解决实战指南 1. 从一次真实的编译翻车说起Android 16 的 AOSP 源码放出来那阵子我第一时间就拉了代码准备在本地跑一遍完整编译。结果source build/envsetup.sh之后lunch选了个aosp_arm64-trunk_staging-userdebugm一敲下去不到十分钟就给我甩了一屏红字。说实话AOSP 编译报错这事儿做过几年系统开发的人都懂——它不是那种“改一行就好”的报错而是往往牵扯到工具链版本、依赖缺失、内存不足、仓库同步不完整等一连串问题。Android 16 作为新版本在构建系统上又做了不少调整比如对 JDK 版本的要求更严、对 Python 环境的依赖更明确、Soong 和 Ninja 的配合方式也有细微变化这些都会让习惯了老版本编译流程的人踩坑。这篇文章就是把我这段时间在 Android 16 AOSP 编译过程中遇到的各种报错、排查思路和最终解决方案系统地整理出来。不管你是刚接触 AOSP 编译的新手还是从 Android 13/14 迁移过来的老手只要你在编译 Android 16 时遇到了报错这里大概率能找到对应的排查方向。我会从环境准备、依赖安装、常见报错分类、具体修复步骤、以及一些非常规问题的排查技巧几个维度展开尽量做到“照着做就能过”。需要提前说明的是AOSP 编译对机器配置有硬性要求。Android 16 的完整编译官方建议至少 16GB 内存实际体验下来 32GB 才比较稳磁盘空间建议预留 400GB 以上源码加编译产物CPU 核心数越多越好。如果你的机器达不到这个门槛很多报错其实是资源不足导致的而不是代码本身的问题。这一点我会在后面的章节里反复提到因为太多人把 OOM 误判成代码错误了。2. 编译环境搭建别在起跑线上摔跤2.1 操作系统与基础依赖的选择AOSP 官方推荐在 Ubuntu LTS 版本上编译Android 16 这个版本我实测在 Ubuntu 22.04 和 24.04 上都能跑通但 24.04 需要额外注意一些包的版本差异。如果你用的是 Docker 容器来编译那基础镜像的选择就很重要——我建议直接用ubuntu:22.04不要用最新的 rolling 版本因为 AOSP 的构建脚本对某些系统工具的版本是有隐式假设的。基础依赖包的安装是第一步也是最容易出问题的一步。Android 16 需要的包比之前版本多了一些下面是我整理的一份完整安装命令sudo apt update sudo apt install -y git-core gnupg flex bison build-essential zip curl \ zlib1g-dev libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev \ libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip \ fontconfig python3 python3-pip libssl-dev libelf-dev \ openjdk-17-jdk-headless repo这里有几个点需要特别说明。第一openjdk-17-jdk-headless是 Android 16 的硬性要求如果你系统里默认是 JDK 11 或者 JDK 8编译到一半会报Unsupported class file major version之类的错误。第二libncurses5和lib32ncurses5-dev这两个包在 Ubuntu 24.04 的默认源里可能已经改名或者被移除了需要额外处理这个我后面会讲。第三repo工具虽然可以用 apt 装但我更推荐用官方脚本安装最新版因为 apt 源里的 repo 版本往往偏旧同步 Android 16 这种新代码库时可能会出问题。2.2 JDK 版本冲突的排查与解决JDK 问题是 Android 16 编译报错里出现频率最高的之一。AOSP 不同版本对 JDK 的要求不一样Android 13 用的是 JDK 11Android 14 开始往 JDK 17 迁移到了 Android 16 就完全要求 JDK 17 了。如果你机器上装了多个 JDK 版本java -version显示的未必是编译时实际用的那个。排查方法很简单在编译前先确认java -version javac -version echo $JAVA_HOME如果java -version显示的不是 17那就要手动切换。我一般用update-alternatives来管理sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-17-openjdk-amd64/bin/java 1 sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/java-17-openjdk-amd64/bin/javac 1 sudo update-alternatives --config java sudo update-alternatives --config javac然后在~/.bashrc里显式设置JAVA_HOME和PATH确保编译终端里用的是 JDK 17。这里有个坑有些系统的JAVA_HOME是在/etc/profile.d/里设置的你改了~/.bashrc可能不生效因为加载顺序的问题。最稳妥的办法是在编译前用export JAVA_HOME/usr/lib/jvm/java-17-openjdk-amd64临时覆盖一下确认没问题后再写进配置文件。注意如果你在 Docker 里编译记得在 Dockerfile 里就把 JDK 17 装好并设好环境变量不要等到容器跑起来再临时装否则每次重建容器都要重来一遍。2.3 Python 环境与 repo 工具的配置Android 16 的构建脚本对 Python 3 的依赖更加明确不再兼容 Python 2。Ubuntu 22.04 默认就是 Python 3.10一般没问题但如果你手动装过其他版本的 Python 并且改了默认指向就可能出问题。检查一下python3 --version which python3确保python3指向的是系统自带的 3.10 或更高版本。另外repo工具本身也是 Python 脚本如果 Python 环境有问题repo sync阶段就会报错。repo 的安装我推荐用官方方式mkdir -p ~/bin curl https://storage.googleapis.com/git-repo-downloads/repo ~/bin/repo chmod ax ~/bin/repo export PATH~/bin:$PATH然后把export PATH~/bin:$PATH写进~/.bashrc。这里有个细节repo脚本第一次运行时会去下载完整的 repo 工具集如果你的网络环境访问 Google 的存储服务不稳定这一步可能会卡住或者超时。遇到这种情况可以多试几次或者找个网络状况好的时段再操作。3. 源码同步阶段的典型报错与处理3.1 repo sync 中断与仓库不完整repo sync是编译前最耗时的一步Android 16 的完整源码加上各种预编译产物同步下来轻松超过 100GB。这个过程里最常见的报错就是网络中断导致的同步失败表现可能是fatal: early EOF、error: RPC failed、fatal: the remote end hung up unexpectedly等等。遇到这类报错第一反应不要慌repo sync是支持断点续传的直接重新跑一遍就行。但如果反复失败就需要调整一些参数repo sync -j4 --fail-fast --no-tags --no-clone-bundle-j4是并发数默认的并发数可能太高导致网络拥塞降到 4 或者 2 会稳很多。--no-clone-bundle是跳过 clone bundle 的下载有时候 bundle 文件下载失败会拖累整个同步过程。--fail-fast是遇到错误立即停止方便你定位问题而不是等所有仓库都跑完才报错。还有一个容易被忽略的点repo sync完成后一定要检查一下有没有仓库处于未完成状态。可以用repo status repo forall -c git status --short如果发现有仓库有未提交的改动或者处于 detached HEAD 状态那编译时大概率会报错。这种情况一般是同步过程中断导致的解决办法是进到对应仓库目录git checkout .清理改动然后重新repo sync那个仓库。3.2 磁盘空间不足的隐蔽报错磁盘空间不足导致的报错往往很隐蔽因为它不会直接告诉你“磁盘满了”而是表现为各种奇怪的编译错误比如No space left on device、write error、甚至是一些看起来完全不相关的链接错误。Android 16 完整编译的磁盘占用我实测下来是这样的阶段磁盘占用源码同步完成约 120GB编译中间产物约 150GB最终镜像产物约 30GB总计峰值约 300GB所以官方建议的 400GB 是有道理的留出余量给 ccache 和临时文件。如果你用的是虚拟机记得检查虚拟磁盘是不是动态分配的动态磁盘在宿主机空间不足时也会导致写入失败。排查方法df -h du -sh /path/to/aosp如果发现空间紧张可以清理掉out/目录里不需要的产物或者用m clean清理编译中间文件。但注意m clean会把所有编译产物都删掉下次编译又要从头来所以慎用。3.3 特定仓库同步失败的单独处理有时候repo sync整体是成功的但某个特定仓库就是同步不下来报错可能是fatal: repository not found或者fatal: unable to access。这种情况一般是仓库路径变更或者权限问题。Android 16 相比之前版本有些仓库的路径确实做了调整比如一些 vendor 相关的仓库被移到了不同的位置。如果你是从旧版本的local_manifest迁移过来的很可能引用了已经不存在的仓库路径。处理办法是先用repo manifest导出当前的 manifest检查一下有没有异常的仓库条目。如果是自己加的local_manifest导致的先把自定义部分注释掉用纯 AOSP 的 manifest 同步一遍确认基础代码没问题后再逐个加回自定义仓库。4. 编译过程中的高频报错分类解析4.1 Soong/Ninja 构建阶段的报错Android 16 的构建系统已经完全基于 Soong 和 Ninja 了make只是外层包装。Soong 负责解析Android.bp文件生成 Ninja 构建规则Ninja 负责实际执行编译。这个阶段的报错通常长这样FAILED: out/soong/build.ninja cd $OUT /usr/bin/ninja -w dupbuilderr -f out/soong/build.ninja ninja: build stopped: subcommand failed.这种报错的关键信息往往在更上面你需要往上翻找到第一个error:开头的行。常见的根因有几类第一类是Android.bp语法错误比如某个模块的依赖写错了、属性名拼错了。这种报错会明确指出是哪个文件哪一行比较好定位。第二类是依赖缺失比如某个模块依赖了一个不存在的模块报错信息里会有module xxx not found之类的提示。这种情况一般是源码不完整或者local_manifest配置有问题。第三类是工具链问题比如clang版本不匹配、javac报错等。这类问题往往和前面说的 JDK 版本、系统工具版本有关。4.2 Java/Kotlin 编译报错的处理Android 16 里 Java 和 Kotlin 代码的编译报错也很常见典型的表现是error: cannot find symbol error: package xxx does not exist这类报错如果出现在 AOSP 原生代码里那基本可以确定是源码同步不完整或者 JDK 版本不对。但如果出现在你自己添加的模块里那就要检查Android.bp里的srcs、static_libs、libs等配置是否正确。还有一个 Android 16 特有的坑新版本对 Java 语言的版本要求提高了有些在老版本能编过的代码在 Android 16 里会因为用了较新的语言特性而报错。比如var关键字、switch表达式等需要确保Android.bp里的java_version设置正确。Kotlin 编译报错则往往和 Kotlin 版本有关。Android 16 用的 Kotlin 版本比较新如果你引入的第三方库是用老版本 Kotlin 编译的可能会有兼容性问题。解决办法是在Android.bp里显式指定 Kotlin 版本或者把第三方库的源码一起编进去。4.3 链接阶段的 undefined reference 报错链接阶段的报错是最让人头疼的因为报错信息往往是一大堆undefined reference to xxx看起来毫无头绪。这类问题的根因通常是某个静态库没有正确链接符号可见性设置有问题架构不匹配比如 32 位和 64 位混编排查方法是从报错信息里找到具体的符号名然后用grep在源码里搜这个符号是在哪个模块定义的再检查那个模块有没有被正确依赖。Android 16 里可以用m module_name单独编译某个模块这样报错信息会更聚焦。如果是架构不匹配的问题检查Android.bp里的compile_multilib和target配置。Android 16 对 64 位的要求更严格了很多模块默认只编 64 位如果你强行要编 32 位可能会缺符号。5. 内存与资源类报错的实战排查5.1 OOM Killer 导致的编译中断内存不足是 AOSP 编译中最常见的“隐形杀手”。表现是编译到某个阶段突然进程被杀终端里可能只留下一句Killed没有任何其他信息。这时候去看系统日志dmesg | grep -i out of memory如果看到Out of memory: Killed process xxx (soong_java)之类的记录那就确认是 OOM 了。Android 16 编译对内存的需求比之前版本更高因为 Soong 在解析阶段会占用大量内存Java 编译阶段更是内存大户。我的经验是内存大小能否编译8GB基本不可能Soong 解析阶段就会 OOM16GB勉强能过但需要加 swap且编译时间极长32GB比较流畅推荐配置64GB非常舒服可以开高并发如果内存不够最直接的解决办法是加 swapsudo fallocate -l 32G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile然后把 swap 写进/etc/fstab让它持久化。但要注意swap 只是应急方案编译速度会慢很多因为磁盘 IO 比内存慢几个数量级。5.2 并发数设置与编译稳定性m命令默认会用所有 CPU 核心来编译但核心数太高反而容易导致内存不足。可以用-j参数控制并发数m -j8具体设多少取决于你的内存和 CPU 比例。一般来说每 GB 内存对应 1 到 2 个并发任务比较安全。比如 32GB 内存设-j16到-j24比较合适。如果你发现编译过程中频繁 OOM就把并发数降下来。还有一个技巧是用soong_ui的--build-from-source参数这个参数会强制从源码编译所有东西虽然慢但更稳定适合排查一些诡异的链接问题。5.3 ccache 配置与编译加速ccache 是 AOSP 编译的加速神器它缓存编译结果二次编译时能大幅缩短时间。Android 16 默认就支持 ccache但需要手动开启export USE_CCACHE1 export CCACHE_EXEC/usr/bin/ccache ccache -M 50G-M 50G是设置缓存大小为 50GB根据你的磁盘空间调整。ccache 的缓存在~/.ccache目录下如果磁盘紧张可以把它移到其他分区。但 ccache 也有坑如果你改了编译参数或者工具链版本ccache 里的旧缓存可能不兼容导致一些奇怪的报错。遇到这种情况用ccache -C清空缓存再编。6. 常见报错速查表与独家避坑技巧6.1 报错速查表报错关键词可能原因解决方向Unsupported class file major versionJDK 版本不对切换到 JDK 17No space left on device磁盘空间不足清理空间或扩容Killed内存不足被 OOM加内存或加 swap降并发fatal: early EOF网络中断重新 repo sync降并发module xxx not found源码不完整检查 local_manifest重新同步undefined reference to xxx链接配置错误检查 Android.bp 依赖ninja: build stopped构建规则错误往上翻找第一个 errorPermission denied文件权限问题检查文件属主和权限python3: command not foundPython 环境缺失安装 Python 3 并设好 PATHrepo: command not foundrepo 未安装或 PATH 不对重新安装 repo 并配置 PATH6.2 独家避坑技巧第一个技巧编译前先跑一遍m nothing。这个命令不会真正编译但会执行 Soong 的解析阶段能提前发现Android.bp的语法错误和依赖缺失。如果m nothing能过说明构建配置没问题再跑完整编译心里就有底了。第二个技巧善用m module_name单独编译。完整编译动辄几个小时如果只是某个模块报错没必要每次都全量编译。用m module_name只编那个模块速度快很多排查问题也更有针对性。第三个技巧保留一份“干净”的 out 目录。我习惯在第一次成功编译后把out/目录打个快照如果磁盘够的话后面如果编译环境被搞乱了可以直接恢复快照省去重新编译的时间。第四个技巧日志重定向。编译输出很长终端里翻页很麻烦建议重定向到文件m -j16 21 | tee build.log然后可以用grep -n error: build.log快速定位所有错误行。第五个技巧注意local_manifest的维护。如果你在 AOSP 基础上加了自定义仓库一定要把local_manifest纳入版本管理并且记录每个仓库的用途和版本。Android 16 的代码变动比较大旧版本的local_manifest很可能不兼容需要逐个仓库确认。7. 一些非常规问题的排查思路7.1 编译成功但镜像启动失败有时候编译过程一切顺利但刷进设备后启动失败卡在开机动画或者直接黑屏。这类问题排查起来比编译报错更麻烦因为编译阶段没有报错信息。首先检查out/target/product/device/目录下的镜像文件是否完整特别是system.img、vendor.img、boot.img这几个关键镜像。如果某个镜像大小异常小那可能是编译时某个模块没编进去。然后看logcat和dmesg的输出如果是内核问题dmesg里会有明显的报错如果是框架层问题logcat里会有FATAL EXCEPTION之类的信息。还有一个常见原因是sepolicy配置问题。Android 16 对 SELinux 的要求更严格了如果新增的模块没有正确配置sepolicy启动时会被拒绝访问导致服务起不来。排查方法是看logcat里有没有avc: denied的日志有的话就根据日志补sepolicy规则。7.2 增量编译的诡异问题增量编译虽然快但有时候会出现一些全量编译不会有的诡异问题比如改了代码但编译结果没变、链接时报符号重复定义等。这类问题一般是依赖关系没更新导致的。解决办法是清理相关模块的编译产物再编m clean-module_name m module_name如果还是不行就清理整个out/目录重新全量编译。虽然耗时但能解决 99% 的增量编译问题。7.3 工具链版本不匹配的隐蔽报错Android 16 对工具链版本有比较严格的要求但报错信息往往不会直接说“工具链版本不对”而是表现为各种奇怪的编译错误。比如clang版本太老会导致某些 C 特性不支持报错可能是unknown type name或者no member named xxx。AOSP 源码里自带了预编译的工具链在prebuilts/clang/目录下。正常情况下编译时会自动用这些工具链但如果你系统里的clang版本更新并且PATH里系统clang排在前面就可能用到错误的版本。检查方法which clang clang --version确保用的是 AOSP 自带的工具链而不是系统安装的。可以在编译前export PATHprebuilts/clang/host/linux-x86/clang-version/bin:$PATH来强制指定。8. 我个人在实际操作中的体会折腾 Android 16 AOSP 编译这段时间最大的感受就是环境准备占 70% 的精力编译本身占 30%。很多报错看起来是代码问题实际上都是环境问题——JDK 版本、Python 版本、磁盘空间、内存大小、网络稳定性这些基础条件没搞好后面怎么调都是白搭。另外一个体会是日志一定要看全。AOSP 编译的报错信息往往是“果”真正的“因”在更上面。很多人看到最后一行报错就开始搜解决方案结果搜到的都是不相关的。正确的做法是从上往下看找到第一个error:出现的位置那才是根因。还有就是不要怕全量编译。增量编译虽然快但排查问题时全量编译更可靠。我现在的习惯是环境有变动或者改了构建配置后先跑一次全量编译确认没问题后面再用增量编译提高效率。最后分享一个小技巧如果你在 Docker 里编译 AOSP建议把源码目录和out目录都挂载到宿主机上这样容器重建时不用重新同步源码和编译。Docker 镜像本身只装环境依赖保持轻量重建起来也快。这个方案我用了很久实测下来很稳特别适合需要频繁切换编译环境的场景。
返回列表