☰
全志Android 10编译实战:环境搭建、内核构建与固件打包
2026/10/21 16:33:40 网站建设 项目流程

全志平台Android 10的编译,我从环境准备到固件打包完整过了一遍。这篇文章把整个流程里容易踩的坑、值得注意的细节、以及一些常规文档里不会写的经验都整理出来,给正准备上手或者卡在编译环节的朋友做个参考。

1. 全志平台Android 10编译到底在编什么

1.1 AOSP、BSP与最终固件的关系

先把这个概念理清楚。Android 10本身是AOSP的一个发行版本,代码量非常庞大。但光有AOSP代码,到了某一颗具体的SoC上根本跑不起来,因为SoC里集成的显示控制器、编解码器、电源管理单元、音频编解码以及各种外设控制器,都需要对应的底层驱动和硬件抽象层来配合。这就是BSP存在的意义——板级支持包,它把芯片厂商对自家平台的适配代码整合进AOSP,形成一套能直接出固件的SDK。

全志平台的Android 10 SDK,从代码结构上看,一般由这几块构成:

目录/模块作用
AOSP主体Android 10系统框架、应用层、系统服务等
内核源码Linux内核及全志平台的设备树、驱动适配
U-Boot引导程序,负责硬件初始化和加载内核
板级配置包含屏幕参数、IO配置、分区表等信息的配置文件
BSP私有目录芯片厂商提供的HAL层、媒体库、电源管理策略等

所以这里说的编译,不是简单build一个AOSP镜像,而是要打通“AOSP主体 + 内核 + U-Boot + 板级配置”这一整条链路,最终产出一个能烧录到板子上、开机进桌面的完整固件。这套流程跑通之后,后续做产品定制才有根基。

1.2 自己编译能解决哪些实际问题

很多人会有个疑问:芯片厂商不是会提供预编译的镜像吗,为什么还要自己搭环境编译?

直接拿预编译镜像确实能启动系统,但仅限于“能用”,到真正干活的时候问题就来了。比如你拿到一块新板子,屏的型号换了,需要在fex或者dts里调整显示参数,预编译镜像根本没法改;你要预装一个系统级应用,要改分区大小,要加一个内核驱动模块,这些都离不开完整的编译环境。

再一个,做方案开发和驱动调试的人必须要有源码级别的环境。编译一次系统,意味着你可以随时改内核、改HAL层、改系统配置,然后快速验证。这种迭代能力,是拿现成固件无法替代的。我自己编译这套Android 10 BSP的初衷也很朴素:需要验证一套新硬件的驱动适配,以及为后续的稳定性测试产出一个可控的基线版本。所以说自己编译不是一个“可选优化”,而是产品定制的前置条件。

2. 编译环境准备:资源、依赖、源码一个都不能少

2.1 硬件资源底线:内存和磁盘最容易被低估

不少人在编译Android 10时,第一个坑就栽在硬件资源预估上。AOSP 10的编译系统已经切换到Ninja + Soong这套架构,编译过程高度并行,对内存的消耗非常夸张。尤其是链接阶段,几个大模块同时链接时,物理内存和Swap会瞬间被打满。

我按照实际体验给一个参考标准:

配置项最低底线舒适建议
CPU核心数8核16核以上
内存16GB32GB
Swap分区16GB32GB,或使用swapfile
磁盘空间150GB200GB以上,强烈建议NVMe SSD

磁盘这块特别容易被忽略。完整Android 10源码同步下来差不多30GB左右,但编译产生的out目录、中间产物、ccache缓存加起来随随便便超过100GB。我见过有人把源码放在一个剩余空间只有80GB的分区里,编译到一半提示“No space left on device”,然后整个构建直接失败。这种问题排查起来最痛苦,因为错误日志看起来像模块编译问题,实际上只是磁盘满了。

内存方面,16GB是能跑,但必须配合足够大的Swap。有一次我在16GB内存的机器上用了24GB的swapfile,全量编译勉强能过,但时间会拖得比较久。后面换到32GB内存的机器,同样的源码,编译速度快了将近一半。原因很简单,swap再快也是磁盘读写,跟内存带宽完全不是一个量级。

2.2 系统与软件依赖安装

系统方面,全志这套Android 10 BSP基本是按Ubuntu 18.04或是20.04 LTS来适配的。我自己用Ubuntu 20.04.6,整体顺利。不建议一上来就冲最新的Ubuntu版本,比如22.04或24.04,因为系统自带的GCC、Make、Python这类基础工具链版本太新,反而容易跟老旧的编译脚本产生兼容问题。一个典型例子:新版本glibc下,某些老旧host工具编译时会报“error: implicit declaration of function”,这种问题改代码性价比很低,换回适配过的系统版本最省心。

基础依赖可以用下面这条命令装齐:

sudo apt update sudo apt install -y git-core gnupg flex bison build-essential zip curl \ zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \ libncurses5 libncurses5-dev lib32ncurses5-dev x11proto-core-dev \ libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc \ unzip fontconfig openjdk-9-jdk

这里有个非常关键的细节:Android 10要求OpenJDK 9。很多人在这一步被老教程误导,装了OpenJDK 8或者OpenJDK 11,然后编译的时候冒出各种“Unsupported major.minor version”错误。其实全志的BSP在build配置里已经指向了AOSP自带的prebuilt JDK路径,编译脚本会在envsetup阶段自动把PATH指过去。只要你没手动干扰JDK环境变量,这个环节不会出问题。要特别留个心眼的是,现代Ubuntu默认可能装的是OpenJDK 11/17,如果你发现编译脚本用的JDK不对,检查一下PATH里有没有被手工设置过。

还有一个Ubuntu 20.04特有的小坑,系统里只有libncurses.so.6,但某些编译工具需要libncurses.so.5,可以手动做一个符号链接解决:

sudo ln -s /usr/lib/x86_64-linux-gnu/libncurses.so.6 /usr/lib/x86_64-linux-gnu/libncurses.so.5

2.3 源码获取:repo 同步与目录结构

全志平台Android 10 SDK的源码管理方式跟标准AOSP一致,用repo工具同时管理几十个Git仓库。初始化时,会要求你先指定一个manifest仓库和分支,这个信息在BSP的发布文档里能找到。

mkdir android10-bsp && cd android10-bsp repo init -u <manifest仓库地址> -b <分支名> repo sync -j4

repo sync这里要给个忠告:不要一上来就用-j8甚至更高。第一次同步的数据量很大,网络稍有波动就容易中断,中断本身不可怕,重新执行repo sync就能续传,但过高的并发下载在弱网环境下会让某个仓库反复超时。我自己习惯用-j4配合一些网络设置,晚上挂机同步,第二天早上基本就好了。

源码同步完成之后,建议先对整个目录结构有个概念,不要急着编译。常见的几个关键目录:

android10-bsp/ ├── build/ # AOSP编译系统主体 ├── device/ # 各产品的板级配置、lunch目标 ├── kernel/ # Linux内核源码 ├── u-boot/ # U-Boot引导源码 ├── vendor/ # 厂商BSP私有代码及预编译库 └── out/ # 编译输出目录(同步完成后还没有)

还有一个容易被忽视的细节:源码目录所在文件系统格式。我之前把代码放在exFAT的移动硬盘上编译,结果各种“Permission denied”和“Text file busy”错误,排查了很久才发现是文件系统不支持Linux的权限模型。请务必把源码放在EXT4格式的本地磁盘或SSD上,这能省掉大量不必要的麻烦。

3. 编译实操:从lunch到pack的完整过程

3.1 初始化环境与目标选择

代码同步完成后,进入源码根目录,第一步就是加载编译环境:

source build/envsetup.sh lunch

lunch命令运行后,会列出当前BSP支持的所有产品编译目标,每个目标对应一套板级方案,命名规则一般类似“平台名_产品名_版本类型”。这里建议第一次就选userdebug版本。原因很实际:userdebug允许adb root,方便后面做内核调试、文件系统修改、日志抓取。eng版本虽然权限更全,但编译出来的系统性能和稳定性偏弱,不太适合作为日常基线。user版本则完全没有root能力,是纯量产形态,不适合开发期用。

选定之后,环境变量里会出现TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_BOARD_PLATFORM等信息。可以执行env | grep TARGET确认一下,只要这些值和你选的lunch目标一致,就说明环境加载成功。

3.2 首次全量编译与进度观察

初始化完成后,推荐直接走全志SDK提供的统一编译入口:

./build.sh

这个脚本内部会依次编译U-Boot、内核和Android系统,最后自动打包。当然,你也可以像标准AOSP那样分步执行:

make -j$(nproc)

两种方式各有适用场景。统一脚本适合第一次全量编译,省心;手动make适合增量编译,你可以精确选择模块范围。

第一次全量编译的耗时,以我用的8核16线程、32GB内存机器为例,大概在2.5到3小时之间。如果机器配置低一些,4到5小时也很正常。有一种错觉是“编译越久说明机器越差”,其实只要不是某一步反复报错,跑完就好。

编译过程中的进度观察有一点技巧。Ninja的编译输出大量使用“^[[?25h”这类控制字符,进度并不像老式make那样直观。我习惯把输出同时写到日志文件里:

./build.sh 2>&1 | tee build.log

然后在另一个终端观察:

tail -f build.log

如果发现长时间卡在同一行,大概率是那个模块编译异常或者磁盘IO拥堵。此时可以打开htop看CPU和内存状态,正常编译过程中CPU占用应该持续在60%以上。

3.3 内核与U-Boot单独编译

真正的产品开发阶段,你会发现大多数修改集中在内核和U-Boot,而不是Android框架。比如换触摸屏驱动、适配新LCD、调整某个外设的GPIO复用,这些全在内核侧。全志的编译入口通常支持单独编译内核:

./build.sh -K

单独编译内核的速度比全量编译快很多,改一个驱动重新编译,通常几分钟就出结果。但这里有个容易翻车的操作:手动把自己编译出来的zImage直接覆盖到out目录里,然后跳过打包脚本去打包。这种做法非常容易导致最终镜像里内核版本和内核模块对不上,表现就是开机直接卡在Android动画,或者WiFi/蓝牙打不开。

正确做法是始终通过SDK脚本或make命令出的产物去触发打包,让打包工具自己去拉取所有模块的镜像。全志的编译框架里,内核、U-Boot和Android各模块之间有依赖关系,打包脚本会根据当前配置重新组合所有镜像,手动替换会破坏这种一致性。

U-Boot也有单独编译的入口,一般在平台SDK下执行:

./build.sh -U

U-Boot编译完之后,打包阶段会把它写入固件前段。这里要特别强调,U-Boot不光是引导程序,它还会在加载阶段读取板级配置里的很多参数,比如屏幕初始化序列、内存频率、启动模式等。所以如果你调整了板级配置,通常需要重新编译U-Boot并重新打包,否则硬件初始化参数不会生效。

3.4 打包镜像与产物核对

整个编译流程的收尾,是全志平台的打包操作。在完成系统编译后,根目录执行:

pack

pack脚本会做几件事:把out目录下的boot、system、vendor、recovery等镜像收集起来,结合U-Boot、内核、板级配置和分区表,生成一个完整的、可以直接烧录到主板eMMC或SD卡里的固件文件。打包完成后,终端会打印生成文件的路径和名字,一般在out目录下,命名类似“sunxi_pack_xxx.img”。

打包这个环节,最常遇到的失败原因有三个。第一个是分区表配置和实际要打包的镜像大小不匹配,比如把system分区设小了,打包脚本会报“img size exceeds partition size”。第二个是依赖的U-Boot或内核镜像缺失,这通常是前面单独编译没跑成功就强行走pack导致的。第三个就是磁盘空间不够,打包过程需要临时写大量文件到out目录。

打包完成后,别急着烧录,先对产物做一次核对:

ls -lh out/target/product/<产品名>/*.img

重点看一下system.img和vendor.img的大小,确认它们小于分区表里标注的大小。同时确认boot.img存在且日期是最新编译时间,避免烧录了旧内核。

4. 编译过程常见问题与排查思路

4.1 环境与依赖类报错速查表

编译过程中大量报错其实都是环境问题伪装成代码问题。我把几种高频故障整理成一张速查表,方便你对照排查:

报错特征真正原因解决方式
Unable to locate packageapt源列表未更新先执行sudo apt update
Unsupported major.minor version 61.0系统JDK版本过新让BSP的prebuilt JDK接管PATH
libncurses.so.5无法打开Ubuntu 20.04只有ncurses6创建ncurses5的软链接
flex: command not found编译依赖缺失安装flex bison
No rule to make target依赖模块未编译或头文件缺失检查是否漏跑前置编译步骤
Permission denied(源码目录内)文件系统不支持权限管理改用EXT4格式本地磁盘

这张表看着简单,真正排查时容易走弯路。比如“No rule to make target”这一条,常发生在你单独编译某个模块,但它依赖的另一个库还没编译的情况。解决思路是先找到报错日志里缺失的目标属于哪个模块,回到根目录用模块名编译,别指望一次make全搞定。

4.2 内存与并行编译导致的隐形问题

AOSP编译系统默认按CPU核心数推断并行度,但内存不够时,高并行反而致命。典型场景是编译到一半,某个模块的子进程被系统OOM Killer杀掉,终端只留下:

cc1: error: out of memory allocating ... Killed ninja: build stopped: subcommand failed.

这种报错不会指向明确的代码错误,但很多人会误以为是自己改了源码导致的,反复检查修改记录,浪费大量时间。实际上只要降低并行度、扩充Swap,问题就解了。

我常用的处理顺序是:先确认物理内存是否满掉了,然后补充Swap,最后再决定是否降低make参数。比如32GB内存的机器,可以设置24GB的swapfile:

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

加完Swap后重新编译,被Killed的模块会重新构建。这里有个小经验:不要一上来就把swap加得特别大,先观察是哪个阶段内存峰值最高,AOSP编译通常是在链接system_server或big boot image时峰值最猛,这一时期swap吃满但只要能挺过去就行。

另外,并行度的选择不是越大越好。make -j后面的数字我建议至少小于等于“CPU核心数 + 2”。我在32GB内存的机器上测试过,-j16和-j8的耗时差别不大,但-j16在链接阶段更频繁地触发内存瓶颈。相比之下,稳定输出比极限压榨更重要。

4.3 修改配置后打包不生效的排查

全志平台的板级配置主要集中在fex文件里,比如sys_config.fex和sys_partition.fex。很多第一次接触全志平台的朋友,修改了显示参数后直接执行pack,烧录后发现完全没变化,就开始怀疑改错文件。

其实全志的配置生效机制分两层。fex文件需要先被转换成二进制的配置格式,然后打包进最终固件里。有些SDK版本里,修改fex后需要执行对应脚本进行转换,而不是直接改完就打包。你可以在源码目录里搜索关键字“fex”,一般能找到转换脚本,比如类似“script”或者是“generate_config”这类工具。

排查思路我建议按这个顺序走:一看修改的fex是不是lunch目标真正引用的那份(经常有人在另一个产品目录下改了配置);二看转换后的二进制配置时间戳是否更新了;三看打包日志里有没有fex相关的警告信息。只要这三步都确认了,配置基本都能正确生效。

另外一个高频问题跟分区相关。修改了sys_partition.fex的分区大小,打包时却提示分区溢出,很大程度上是因为你只改了分区表,但对应镜像的实际大小没控制好。这里可以用一个问题自检:你编译出来的system.img,是比原来的大还是小?如果比分区表里预分配的大,就得缩减系统内容,或者增大对应分区,两者得同步改。

5. 编译之后的定制与提速思路

5.1 ccache 与增量编译的配置技巧

第一遍全量编译成功后,接下来就是高频的增量编译阶段。省时间的核心工具是ccache,它缓存了C/C++编译的中间结果,对重复编译同类代码提速非常明显。

配置ccache的方法很简单:

export USE_CCACHE=1 export CCACHE_DIR=/work/ccache/android10 ccache -M 80G

注意这里有个容易被忽视的点:ccache缓存目录要放在一个够大且读写快的磁盘上,如果放到了和out目录同一块机械盘上,加速效果会打折扣,因为缓存的读取和写入都在同一块盘上抢IO。我自己习惯把ccache放在另一块SSD上,编译速度提升明显。

ccache还有一个使用技巧:当你重新sync代码,或者切换lunch目标之后,建议清空ccache重新开始缓存。不同平台的内核代码大面积不同,旧缓存命中率会急剧下降,而且偶尔会因为“脏缓存”导致编译出奇怪的问题。清理命令:

ccache -C

增量编译本身也很讲究,不需要每次全量make。改动了framework层,直接编译system镜像就行:

make systemimage -j16

改动了某个app,用单模块编译加push组合,速度远超全量编译。这种“模块级迭代”的思路,是日常开发效率的关键。

5.2 产品定制最常动的三个配置位置

编译流程稳定之后,真正有意思的部分才开始。产品定制时,全志平台最常动的三个位置,我列一下:

第一是fex板级配置。屏幕分辨率、触摸屏类型、GPIO复用、串口调试开关,这些都可以在sys_config.fex里调。它就像一套看不见的连线表,U-Boot和内核启动时都会读取。改屏参的时候,lcd0相关配置要仔细,一个像素时钟参数错了,轻则花屏,重则黑屏。

第二是内核设备树。某些外设没有配置在fex里,而是在dts里描述,比如I2C设备、SPI设备、PWM背光等。改dts后通过编译内核、打包烧录来验证。这里建议每次改完dts,把对应的dts编译产物路径记下来,万一打包后效果异常,可以直接看这个文件实际生成了没。

第三是系统内预置应用。要把第三方APK做成系统应用,可以放到vendor分区目录,配合Android.bp或Android.mk编写安装规则,或者直接放预编译APK到对应目录。这种方式产出的应用,用户无法卸载,是很多设备类产品的刚需。

5.3 烧录验证与整机调试建议

固件打包完成,最激动的一步是烧录验证。全志平台常用PhoenixSuit工具,在Windows下通过USB连接开发板,一键烧录镜像。Linux环境下,可以用平台提供的烧录脚本或者fastboot模式进行操作。

PhoenixSuit烧录流程: 1. 打开工具,加载打包出来的img固件 2. 开发板进入烧录模式(一般按住下载键再上电) 3. USB连接PC,工具识别后自动烧录 4. 烧录完成,板子自动重启

首次启动建议不要急着看Android桌面,优先抓串口日志,判断启动卡在哪一阶段。全志平台开发板一般引出UART调试口,波特率常见是115200,用串口工具连接后能看到完整启动流程:U-Boot阶段、内核阶段、Android用户空间阶段。如果卡在内核,重点看内核日志里有没有dts解析错误或驱动probe失败;如果卡在Android动画,要看系统服务有没有crash。

烧录之后,用adb快速验证基本功能:

adb devices adb root adb shell getprop ro.build.version.release

确认Android 10版本号、串号、网络、显示这些基础项正常,整个编译流程才算真正闭环。

写到这里,全志平台Android 10编译从环境搭建到固件产出、再到定制验证的主线流程,基本都覆盖了。最后再分享一个我在反复实践中总结的小技巧:不要在你的工作机上只保留一套编译环境,最好把“干净未编译的源码目录”和“正在开发的分支”分开,前者用来验证SDK原始状态,后者专门做定制修改。这样一旦出现诡异问题,可以先切回干净目录确认是代码问题还是环境问题。

编译的过程确实枯燥且漫长,但当你第一次烧录自己编译的固件,看到开机动画亮起的那一刻,前面掉的头发、排的错都是值得的。至少,从这一步开始,整个系统的行为就完全在你的掌控之下了。

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

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

立即咨询