☰
全志Android10编译从零到一:环境配置、常见报错排查与烧录验证
2026/10/21 6:53:44 网站建设 项目流程

1. 编译全志Android10之前,先想清楚这几个问题

做全志平台Android10的系统编译,老实说不是个"拿来就能跑"的活。很多第一次接触芯片原厂BSP(Board Support Package,板级支持包)的朋友,拿到源码包的第一反应是:解压,然后敲source build/envsetup.sh,再lunch选一个产品,最后make -j16,完事。但实际走下来你会发现,这套流程放在高通、联发科、全志这些不同平台上,细节差异非常大。全志的Android10源码从内核、U-Boot到AOSP(Android Open Source Project,安卓开源项目)上层是一整套独立维护的体系,编译前如果不把环境、磁盘、内存这些基础盘明白,后面随便一个报错都能卡你一两天。

这篇文章我就以自己的实际操作为主线,从零开始讲清楚全志平台Android10编译的完整路径,包括环境准备、源码同步、编译配置、常见报错排查,以及最后怎么把产物烧到板子上验证。内容偏工程实操,适合三种人看:一是刚接触全志方案、准备自己搭编译环境的开发者;二是已经在编译但频繁踩坑、想系统梳理一遍的嵌入式工程师;三是对BSP整体流程感兴趣、想知道一次完整编译背后到底发生了什么的技术爱好者。

先说一个反直觉的结论:决定编译成败的核心因素,往往不是CPU核心数,而是内存大小和磁盘剩余空间。全志Android10的完整编译,AOSP部分加内核加U-Boot算下来,源码解压后大约需要200GB左右的磁盘空间(含编译中间产物),内存推荐最低16GB,低于这个数很容易在Jack编译器或ld链接阶段被杀进程。我见过有人在8GB内存的机器上硬扛,每次都卡在libart相关模块的链接上,折腾了三天最后发现是内存不足导致的OOM(Out Of Memory,内存耗尽)而不是代码问题。

所以,在动手之前,先老老实实把下面这张环境清单过一遍。这是我在多台机器上反复验证后觉得比较靠谱的基础配置,照着准备基本不会在环境层面翻车。

项目最低要求推荐配置说明
操作系统Ubuntu 16.04+Ubuntu 18.04/20.04 LTS全志SDK官方多基于Ubuntu验证,其他发行版需自行适配
CPU8核16核及以上编译时长差距明显,8核全编译约3小时,16核约1.5小时
内存16GB32GB低于16GB容易出现OOM,尤其AOSP的Jack/R8阶段
磁盘300GB机械硬盘500GB SSD或NVMe源码+out产物约200GB,SSD明显缩短IO等待时间
Shellbashbash不要用zsh或fish跑BSP脚本,部分脚本兼容性不好

另外还要注意:编译主机的发行版不要盲目追新。全志官方SDK的很多脚本是几年前写的,依赖的是老版本的python2、openjdk-8和一系列gnu工具链,Ubuntu 22.04之后系统默认移除了一些老库,你得自己手动补。我建议直接装Ubuntu 18.04,后面会省很多事。如果你非得用新版本,那也要先把依赖配好,别指望原厂脚本能自动适配。

2. 编译前的硬件与基础环境准备,一步都别省

2.1 内存与磁盘的硬性门槛

从实际编译来看,全志Android10整个过程中有两个阶段对内存非常敏感:

第一个是源码同步后的首次代码扫描阶段,build/make会通过Soong生成Ninja构建文件,这个阶段会加载大量模块的Android.bp、Android.mk,内存占用瞬间冲高。16GB内存的机器勉强能看到进度,32GB以上会比较从容。

第二个是链接阶段,尤其是art、libcore这种大型C++库的链接,ld.lld或Gold Linker会一次性加载大量符号表,内存不够直接触发OOM,日志里会有Killed字样,或者你看到进程还在但系统已经swap到几乎卡死。

磁盘方面,out目录里最占空间的是各个模块的中间产物(.o文件)、dex相关文件、system.img等镜像文件,加到一起相当可观。另外建议给out目录留出至少80GB的闲置空间作为临时缓冲,否则编译到最后生成signed images时磁盘满了,前面努力全白费。

提示:编译前先跑一下df -h确认磁盘余量,再跑free -h确认内存。如果内存不够,一个临时方案是加大swap分区,虽然会拖慢编译速度,但至少不会中途崩掉。

我在一台8核16GB的机器上做过测试,加了16GB的swap之后,原本每次都失败的libart链接阶段能勉强通过,但编译总时长比正常机器多了将近40%。所以swap只能救急,不建议当作长期方案。

2.2 Ubuntu下的依赖包清单

在全志平台上编译Android10,先解决基础依赖。不同Ubuntu版本装的包略有差异,我这边以Ubuntu 18.04 LTS为例,以下是一份经过验证的依赖安装命令:

sudo apt-get update sudo apt-get install -y \ git-core gnupg flex bison build-essential zip curl \ zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \ lib32ncurses5-dev x11proto-core-dev libx11-dev \ lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc \ unzip fontconfig openjdk-8-jdk \ python python-dev python3 python3-dev \ libssl-dev libncurses5-dev libreadline-dev \ bc u-boot-tools kmod

这里面有几个很容易漏掉的,我单独挑出来说一下:

  • lib32ncurses5-dev和lib32z1-dev:编译过程中不少历史遗留的host工具是32位二进制,缺少32位库会直接报No such file or directory,但文件明明是存在的,这种"幽灵报错"通常就是缺32位库。
  • openjdk-8-jdk:Android10官方要求OpenJDK 8,全志SDK把Java版本校验写死在build/envsetup.sh或check_boot相关的脚本里了,你装OpenJDK 11或17,校验阶段就会被卡住,即使勉强绕过,后续签名工具的行为也可能不一致。
  • python:虽然全志Android10的编译主线已经迁移到Python3,但repo工具早期版本还是Python2的,如果没有python这个命令(Ubuntu 20.04之后默认只装python3),repo会直接起不来。

装好依赖之后,我还要建议做一件很多文档没提的事:把默认shell固定为bash,并且把编译用户的ulimit调高:

ln -sf /bin/bash /bin/sh echo "ulimit -s 32768" >> ~/.bashrc ulimit -u 65535

不调ulimit -s(栈大小限制)的话,链接某些模块时可能报栈溢出,这个坑在交叉编译工具链上特别常见。

2.3 编译用户与目录权限

另一个很多人忽略的点是编译用户权限和目录所有者。全志SDK的buildroot、aosp、kernel几个目录在编译过程中会产生大量符号链接,如果用sudo来编译,这些链接的所有者会变成root,后续用普通用户增量编译时,权限冲突会引发各种莫名其妙的问题,比如Permission denied或者奇怪的broken symlink。

最省心的做法是:用普通用户身份编译,确保源码目录归属于当前用户:

sudo chown -R your-username:your-username /path/to/source

全程不要用sudo make。你需要root权限的只有安装依赖包和后续烧录工具时的那几次。

3. 源码同步与工具链环境的搭建细节

3.1 从原厂SDK到本地源码的完整流程

全志Android10的源码分发方式通常是两种:一种是直接提供一个打包好的SDK压缩包,解压即用;另一种是通过repo从服务器同步,.repo目录里记录了所有子项目的Git仓库。第二种方式比较正规,也方便后续做定制化修改后提交到自己的仓库管理。

如果你的SDK包里有.repo目录,可以用下面的方式重新完整同步(前提是能正常访问原厂镜像服务器):

mkdir -p /opt/allwinner/a10_android10 cd /opt/allwinner/a10_android10 repo init -u <manifest仓库地址> -b <分支名> --repo-url=<repo镜像地址> repo sync -c -j8 --no-tags

-c参数表示只下载当前分支的代码(不拉取所有远程分支的引用),能省不少时间和磁盘;--no-tags同理,不拉标签信息。这两个参数在源码体积很大的情况下非常有用。

同步完成之后检查一下目录,正常情况下你会看到aosp、kernel、u-boot、buildroot(或brandy)这几个一级目录,外加一些脚本和配置文件:

ls -la # 预期输出大致包含: # aosp/ brandy/ buildroot/ kernel/ scripts/ tools/ .repo/

这里要特别提一下brandy(也叫brandy-2.0),这是全志特有的一个目录,里面是U-Boot和Arm Trusted Firmware(ATF)的源码和构建脚本,它的编译流程完全独立于AOSP,需要单独执行。很多第一次上手的人只盯着aosp和kernel,到最后发现烧录的boot0和U-Boot根本没更新,问题就出在没编brandy。

3.2 U-Boot、Kernel、AOSP三者的编译顺序

全志Android10的最小编译顺序是:brandy(U-Boot/ATF)→ kernel → AOSP。这个顺序不是随便排的,有实际的依赖逻辑:

  • AOSP的mkimage步骤需要用到U-Boot生成的环境变量、分区表和boot资源文件;
  • 内核的dtb(设备树二进制)会被打包进boot.img或recovery.img,而AOSP的构建脚本会在指定路径去找这些产物;
  • buildroot通常用于生成根文件系统里的一些用户态工具,如果你不做系统裁剪,它可以先放着不编,但U-Boot和Kernel省不了。

在源码根目录一般有一个build.sh或envsetup.sh脚本,它会一次性编排上述顺序,但我个人习惯分开跑,出问题了更好定位。下面是一个典型的全志平台编译入口:

source build/envsetup.sh lunch 2 # 选择目标产品,实际数字根据你的lunch菜单来 make -j16

这套流程在AOSP本体层面没问题,但不能覆盖U-Boot和Kernel的定制签名逻辑。全志官方SDK一般提供了更上层的封装脚本,比如:

./build.sh -p <产品名> -k <kernel配置>

具体参数因SDK版本而异。我的建议是:先读一遍根目录的README或build.sh的注释,搞清楚它默认帮你做了哪些事,再决定是直接用还是拆开自己跑。

3.3 建议一次编全,还是分步增量编?

我的习惯是第一次全量编,之后尽量用增量。原因很简单:Android的构建系统(Soong/Ninja)已经非常成熟了,增量编译只要你不手动删out目录,它自己会通过文件时间戳和依赖关系判断哪些需要重编,比人肉判断靠谱得多。

但有几个情况必须手工干预:

  • 修改了内核配置(defconfig或dts设备树),需要先单独编内核,再把产物拷贝到AOSP的kernel目录下,否则AOSP会用它缓存的旧内核;
  • 修改了分区表或打包脚本,必须清理相关镜像的中间产物(不是整个out),用rm -rf out/target/product/<产品名>/system.img这类定向清理更好;
  • 切换了lunch产品方案,如果目标产品架构(比如从32位切到64位)变了,最好直接rm -rf out,否则会有一堆ABI不匹配的link错误等着你。

我把分步手工编译的操作写出来,给想精细控制的人参考:

# 1. 编译U-Boot/ATF(brandy目录下) cd brandy/brandy-2.0 ./build.sh -p <产品名> # 2. 编译内核 cd kernel make ARCH=arm64 <产品名>_defconfig # 以ARM64为例 make ARCH=arm64 -j16 Image dtbs # 3. 拷贝内核产物到AOSP指定位置(路径以SDK实际为准) cp kernel/arch/arm64/boot/Image arch/arm64/boot/dts/xxx.dtb \ ../aosp/device/softwinner/<产品名>/kernel/ # 4. 回到AOSP根目录全量编译 cd ../aosp source build/envsetup.sh lunch <产品名>-userdebug make -j16

4. Android10特有的编译流程与配置项

4.1 为什么Android10比Android9更吃配置

从工程角度看,Android10(API 29)相比Android9最大的变化是强制使用Soong构建系统,Android.mk的兼容模式虽然还存在,但大量系统模块已经迁移到Android.bp。Soong会把所有模块的Android.bp解析成build.ninja,再加上Kati对Android.mk的转换,整个构建图的规模比Android9大了一圈。

还有一个容易被低估的耗时点是R8/ProGuard。Android10开始,系统应用的混淆/压缩默认启用了R8,全志的AOSP里system_server和framework.jar的构建阶段,R8的耗时和内存占用都不小。如果内存不够,这里很容易卡死或者Killed。

另外Android10在编译时会执行系统镜像签名验证,它默认用的avbtool(Android Verified Boot 2.0)要求所有VBMeta和image分区都带上正确的哈希和签名信息。这意味着你哪怕只改了一个APK,最后的make image阶段也要重新对整个系统镜像做哈希树计算,这个阶段属于纯CPU密集,没有太多并行度可言的瓶颈点。

4.2 必查的lunch菜单与产品形态选择

全志Android10的lunch菜单大体上分为工程师版本(-userdebug)、量产版本(-user)和测试版本(-eng),在实操中不同版本差异很明显:

版本adb root系统应用可调试性镜像大小适用场景
eng默认开启高大日常开发调试
userdebug可开启较高中测试/预发布验证
user关闭低小量产打包

我的建议是日常开发用-userdebug,别用-eng。虽然eng很方便,但它的编译产物里多了很多调试符号和测试工具,系统镜像偏大,而且有些性能问题和ANR(Application Not Responding,应用无响应)只在-userdebug和-user下才能复现。等到需要做性能测试或稳定性验证时,再编一版-user。

4.3 内核与设备树的选择逻辑

全志Android10的内核通常是4.9或5.4,这取决于具体芯片方案。全志把内核配置放在kernel/arch/<架构>/configs/下,设备树在arch/<架构>/boot/dts/sunxi/下,文件名一般以芯片型号开头。

编译之前先确认两件事:

第一,产品配置里到底引用的是哪颗芯片。有的开发板虽然名字叫某型号,但因为改版用过同系列的另一个芯片,设备树选错了,编译能通过,烧进去外设全部失灵,比如Ethernet不通、LCD无输出,排查起来特别费劲。

第二,设备树和内核配置的对应关系。AOSP里的BoardConfig.mk会指定TARGET_KERNEL_DTCONFIG,它决定编内核时用哪个defconfig。如果你手工编内核时选的defconfig和AOSP期待的不一致,镜像里的内核和后续系统加载的模块可能出现不匹配,比较典型的现象是某些驱动模块加载失败。

5. 编译过程中的常见报错与逐类排查

5.1 OOM与进程被杀的辨别方法

其实编译过程里真正让人头疼的报错,很多时候不是"语法错误",而是环境层面的隐性故障。我按照踩过的坑频率,把最容易遇到的报错类型排了个序,附上对应的排查方法,方便你对照。

第一个高频问题就是编译过程中某个进程突然消失,终端只显示:

Killed

或者:

ninja: build stopped: subcommand failed.

这时候别急着怀疑代码,先去查内存:

dmesg -T | grep -i oom

如果输出里有Out of memory: Kill process的记录,基本可以确认是内存不足。处理方式除了加物理内存和加swap之外,还有一个收效显著的操作:限制Ninja的并行任务数。

make -j4 # 在8核/16GB机器上,用-j4不会比-j16慢太多,但稳定得多

并行度降下来之后,虽然单次编译时间会变长,但进程不会互相抢内存抢到崩,整体通过率反而更高。这是"慢即是快"的典型场景。

5.2 Jack Server相关问题

在Android10之前的版本,Jack Server问题几乎是所有编译者的噩梦,好在这套方案在Android10里已经逐渐退出主舞台。但有些全志SDK版本可能还保留了Jack的痕迹,如果你遇到Jack server failed,最彻底的办法是禁用Jack:

export ANDROID_FORCE_JACK=false

Android10默认已经不依赖Jack了,不用顾虑。

5.3 缺少32位库的"幽灵报错"

这一类报错非常有欺骗性,典型表现是执行某个host工具时直接提示:

bash: prebuilts/.../bin/xxx: No such file or directory

但ls一看文件明明在。这几乎可以断定是动态链接器缺失,也就是32位host工具找不到32位的ld-linux.so。解决办法就是补装前面提到的lib32z1-dev、lib32ncurses5-dev等32位兼容库。

5.4 模块间依赖与脏编译的连锁问题

在AOSP增量编译时,偶尔会碰到一处报错与当前改动的文件毫无关系,比如你改了一个HAL(Hardware Abstraction Layer,硬件抽象层)文件,结果报错却是libbinder的某个函数找不到符号。这种情况通常发生在跨模块依赖的头文件发生了变化,但没有触发级联编译。

最有效的排查路径是这样的:

  1. 先看报错模块和改动文件是否在同一依赖树里:grep -r "你改的头文件路径" out/soong/.bootstrap/*.ninja
  2. 如果没有关联,再检查是否有人改了低级库的接口:查看out目录里对应模块的时间戳;
  3. 实在查不出来,就直接定向清理再重编,不要轻易rm -rf out:
rm -rf out/target/product/<产品名>/obj/lib/libbinder* make -j16

定向清理比全量清理快得多,而且不影响其他模块的增量状态。

5.5 编译时间过长时的阶段判断

编译正常进行时,整个流程大体分成几个阶段,我列个时间分布供你参考(以16核32GB机器、SSD盘为例):

阶段大概耗时说明
build.ninja生成15-20分钟解析所有模块,生成构建图
内核/驱动编译20-30分钟独立于AOSP进行
framework/art编译40-60分钟最大占比,多为C++编译
系统应用dex打包15-20分钟R8/ProGuard阶段
镜像打包/签名10-15分钟生成system.img等

如果你卡在某个阶段超过正常时间的1.5倍,建议先检查是不是swap在用、磁盘IO是不是满了,再考虑是不是模块本身编译慢。使用top和iotop实时观察资源占用,比干等更有意义。

6. 固化到硬件前的镜像检查与烧录验证

6.1 编译产物清单与必要校验

编译完成后,在out/target/product/<产品名>/目录下会生成一堆镜像文件。对全志平台来说,你最需要关心的是下面这几个,以及它们分别对应什么分区:

镜像文件对应分区作用不烧的后果
boot0_sys.fexboot0芯片启动第一阶段引导芯片直接起不来
u-boot.fexU-Boot引导内核只能烧后重刷
boot.imgboot内核+ramdisk系统起不来
system.imgsystemAndroid系统主体功能不全/系统异常
recovery.imgrecovery恢复模式无法进入Recovery
vbmeta.imgvbmetaAVB校验信息校验不过会进不了系统

盲目把所有镜像一起刷进去是不对的。正常开发阶段一般只需要更新boot.img、system.img、vendor.img这几个,而烧错boot0_sys.fex或u-boot.fex的风险远大于收益,一旦写坏了U-Boot,救砖会很麻烦。

在烧录之前,我还建议对镜像做一次基本信息检查,避免把损坏产物误烧到板子上:

file system.img du -h system.img # 检查能否正确挂载 mkdir -p /tmp/system_mount sudo mount -o loop system.img /tmp/system_mount df -h /tmp/system_mount

file命令能看出镜像到底是EXT4还是稀疏镜像,mount -o loop能验证镜像内部文件系统没有损坏。这一步只要一分钟,但能拦住很多"烧进去起不来"的倒车事故。

6.2 全志烧录工具与分区表匹配

全志平台的PC端烧录工具(类似PhoenixSuit或其他全志专用烧录工具)用起来整体不算复杂,但有一个细节必须注意:烧录工具的版本要和固件的分区表格式匹配。如果你改过分区大小(系统镜像从2GB改成3GB),但烧录工具烧的固件头信息还是按旧分区表来的,可能出现开机后system空间不足,或者系统挂在开机动画处反复重启。

还有一种常见手动烧录方式是fastboot:

sudo fastboot flash boot boot.img sudo fastboot flash system system.img sudo fastboot reboot

但全志平台的fastboot支持度要看U-Boot有没有开启USB fastboot功能,有些量产固件默认是关掉的,这时只能用PC端工具。

6.3 首次开机日志与启动阶段判定

烧录完成后第一次开机,不要只盯着屏幕看,我的习惯是同时把串口日志拉出来:

  • 看boot0有没有正常跳转;
  • 看U-Boot有没有读取到环境变量并加载内核;
  • 看内核日志里有没有Kernel panic - not syncing;
  • 看init进程有没有挂载system分区,以及zygote有没有正常起来。

如果你手边没有串口线,可以通过adb shell进入系统后用logcat抓Android层日志。但如果是boot0或U-Boot阶段挂了,串口日志是唯一的判定手段,这一套工程调试工具还是要备齐。

7. 把编译速度与产物可维护性一起优化

7.1 使用ccache缓存重复用的编译结果

Android10的AOSP代码量非常大,即使增量编译,重复编译同一个文件的情况依然很多(比如切换不同产品或者反复修改少量文件)。ccache是解决这类问题的标配工具:

sudo apt-get install ccache export USE_CCACHE=1 export CCACHE_DIR=/data/ccache ccache -M 100G

对于全志Android10来说,ccache缓存设在100GB左右比较好,太小的话命中率上不去,效果不明显;太大的话又浪费磁盘。我自己的经验是ccache开启后,全量编译第三次以后的速度能提升30%左右,增量编译更是快到体感明显。

有一个坑要提醒:如果你换了内核工具链或编译器版本,旧的ccache缓存可能会带来奇怪的旧代码残留。遇到id不明的编译问题时,优先ccache -C清空一次再试。

7.2 关闭不必要的并行内存大户

16核以上的机器在编Android10时,Ninja默认会拉满所有核。但全志的SDK里有些老旧的host工具(比如mkbootimg、fs_config)是串行工作的,它们会把线程池卡死,导致CPU看起来占用不高,但同一时间跑了很多任务抢内存。

这类阶段可以使用:

# 限制Ninja最多同时跑的进程数,但不直接限制核心数 make -j16

如果机器是16核、内存32GB以上,直接-j16没问题;如果内存偏紧,建议限制在-j8甚至-j6,会比默认拉满更稳定。

7.3 保存产物备份,而不是无限重编

全志Android10的out目录动辄上百GB,一旦某个阶段编坏,反复清理重建的时间成本很高。我的做法是:每个稳定版本编译通过后,把最终镜像和模块产物拷贝到单独的备份目录,比如:

mkdir -p /backup/2024xxxx_android10 cp -r out/target/product/<产品名>/*.img /backup/2024xxxx_android10/

这样下次需要还原已知稳定固件时,直接烧备份的镜像即可,不用重新编译。源码改坏了要回退时,也直接可以git checkout某个commit重新增量编译,不必清理整个out。

无论你编的是量产固件还是开发板固件,这套备份思路都能帮你省下很多无意义的重编时间。

8. 最后关于全志平台编译的个人体会

老实说,全志Android10的编译流程在国产芯片平台里算比较规范的,但它的"坑"在于文档零散,很多细节散落在SDK内部脚本、原厂Wiki和各个社区帖子里,没有一份统一的白皮书。这篇文章写到的内容,是我自己在多次编译、烧录、救砖过程中逐步沉淀下来的关键点,特别把环境、顺序、报错排查和产物验证这几块串在了一起。你按照这套路径走,不能说100%不踩坑,但至少能避开我踩过的那一大半。

如果在实际操作中遇到本文没覆盖到的报错,我建议你先抓住两条线索:一看内存和磁盘资源,二看编译日志最早的报错点。大部分编译问题都能靠这两步缩小范围。编译环境这种事,跑通一次全流程之后,后面就轻车熟路了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询