系列目录:Android 7系统编译与刷机(一):源码环境搭建与完整编译流程 | (二):Image镜像生成机制深度解析 | (三):从编译产物到高通刷机包 | (四):QFIL刷机工具详解 | (五):特定分区镜像的单独导出 | (六):特定分区刷入实战


一、为什么要学编译流程

你可能会问:现在满大街都是刷机包,直接下载一个刷进去不就行了?为什么还要自己编译?

三个场景,你大概率遇到过至少一个:

  • 场景一:你改了 Settings 里的一行代码,发现 make 全编要跑两个小时,只想验证这一个 APK 的改动——怎么只编译一个模块?
  • 场景二:你拿到了一个第三方 ROM,发现 build.prop 里有个参数不对劲,想改完重新打包一个 system.img——但不知道这个 .img 到底是怎么从一堆文件夹变成单个文件的。
  • 场景三:你刷了机发现 data 分区大小不对,想重新分区——但不知道分区表是怎么定义的,也不知道编译参数 BOARD_SYSTEMIMAGE_PARTITION_SIZE 和最终的 system.img 之间是什么关系。

这些问题,网上搜到的答案要么是"执行 make -j8 就行了",要么是"用 make_ext4fs 命令"。但为什么是这个命令?参数从哪来?编译系统的 Makefile 是怎么串联起来的?没有人讲清楚。

本系列的目标就是把从源码到刷机的完整链路讲透,让读者不仅知道"怎么做",更知道"为什么这样做"。


二、编译环境准备

2.1 硬件要求

AOSP 7(Android Nougat)源码加上编译产物,建议硬盘空间至少预留 150GB。内存建议 16GB 以上,否则 jack-server 很容易 OOM。

关键:Android 7 的编译工具链是最后一次大规模使用 jack(Java Android Compiler Kit)的版本,jack-server 默认最大堆内存只有 4GB,编译大型项目时很容易挂掉。建议在 ~/.jack-settings 中调大 JACK_SERVER_VM_ARGUMENTS。

2.2 操作系统与依赖

Android 7 官方推荐 Ubuntu 14.04 / 16.04(64 位)。虽然更高版本也能兼容,但编译中可能出现 glibc 版本导致的不兼容问题。

# 安装必要依赖包(Ubuntu 16.04)
sudo apt-get install -y git-core gnupg flex bison gperf build-essential \
  zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \
  lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev \
  libgl1-mesa-dev libxml2-utils xsltproc unzip

2.3 JDK 版本

Android 7 需要 OpenJDK 8。注意:Android 6 及之前用 JDK 7,Android 8 及之后用 JDK 9+,版本错一个就会编译失败。

sudo apt-get install -y openjdk-8-jdk
# 确认版本
java -version  # 应输出 1.8.0_xxx

三、源码下载:Repo 工具与国内镜像

3.1 Repo 工具是什么

Repo 是 Google 为管理 AOSP 这种多 Git 仓库项目开发的工具。它本身是一个 Python 脚本,通过一个 default.xml 清单文件描述所有子仓库的地址和分支。

aosp/
├── .repo/          ← repo 元数据,清单文件在这里
│   └── manifests/
├── build/          ← 构建系统(独立 git 仓库)
├── frameworks/     ← 框架层(独立 git 仓库)
├── packages/       ← 应用层(独立 git 仓库)
├── system/         ← 系统核心(独立 git 仓库)
└── ...             ← 200+ 个子仓库

3.2 使用清华镜像

直接从 Google 源下载速度极慢,推荐使用清华大学镜像站。

# 1. 下载 repo 工具
mkdir ~/bin
curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o ~/bin/repo
chmod +x ~/bin/repo
export PATH=~/bin:$PATH

# 2. 初始化仓库(指定 Android 7.1.2 分支)
mkdir aosp7 && cd aosp7
repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest \
    -b android-7.1.2_r36

# 3. 同步源码(根据网络情况,耗时 2~8 小时)
repo sync -j4

注意:-j4 控制并发下载数,清华镜像站对并发连接有限制,开太高反而会被限速。建议先从 -j4 开始,稳定后再调高。


四、编译三部曲:envsetup → lunch → make

这是每个 Android 开发者都背过的三行命令:

source build/envsetup.sh
lunch
make -j8

但背后发生了什么?我们来拆解。

4.1 source build/envsetup.sh — 环境初始化

源码路径:build/envsetup.sh

这个脚本做三件事:

  1. 注入 shell 函数到当前会话:lunch、m、mm、mmm、croot、godir 等
  2. 加载设备相关的 vendorsetup.sh(如果有的话)
  3. 设置环境变量 BUILD_SYSTEM,指向 build/core/
# 核心逻辑(简化)
function m() {
    # 一键编译,相当于 make
    local T=$(gettop)
    if [ "$T" ]; then
        make -C "$T" $@
    fi
}

function mm() {
    # 编译当前目录下的模块
    local T=$(gettop)
    local SUBDIR=`pwd | sed "s,$T/?,,"`
    local BUILD_ID=""
    if [ -f "${SUBDIR}/Android.mk" ]; then
        BUILD_ID="$SUBDIR:all"
    fi
    make ${BUILD_ID} $@
}

关键设计:envsetup.sh 不执行任何编译,只是在 shell 环境中注入函数。这意味着 source 之后你可以在任何目录下直接敲 m、mm、mmm,而不需要切回源码根目录。

4.2 lunch — 选择编译目标

lunch 命令会列出所有可用的编译目标,格式为 产品名-编译类型。

You're building on Linux

Lunch menu... pick a combo:
     1. aosp_arm-eng
     2. aosp_arm64-eng
     3. aosp_mips-eng
     4. aosp_mips64-eng
     5. aosp_x86-eng
     6. aosp_x86_64-eng
     ...

以下表格对比了三种编译类型的区别:

编译类型说明典型用途
eng工程版本,包含所有调试工具,adb root 默认可用日常开发
userdebug用户调试版本,adb root 需手动开启测试验证
user正式发布版本,无 root 权限量产发布

lunch 执行后,会设置以下关键环境变量:

TARGET_PRODUCT=aosp_arm64        # 目标产品
TARGET_BUILD_VARIANT=eng         # 编译类型
TARGET_BUILD_TYPE=release        # 构建类型
PRODUCT_OUT=out/target/product/aosp_arm64  # 产物输出目录

4.3 make -j8 — 编译入口

make 不带参数时,默认目标是 droid,定义在 build/core/main.mk 中。

源码路径:build/core/main.mk

# 默认目标(必须第一个声明)
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL): droid_targets

# droid_targets 聚合所有构建目标
.PHONY: droid_targets
droid_targets: droidcore dist_files

# 构成 droidcore 的镜像目标($(PRODUCT_OUT) = out/target/product/<设备名>)
droidcore: files \
    systemimage \
    $(PRODUCT_OUT)/boot.img \
    $(PRODUCT_OUT)/recovery.img \
    $(PRODUCT_OUT)/userdata.img \
    $(PRODUCT_OUT)/cache.img \
    $(PRODUCT_OUT)/vendor.img \
    $(PRODUCT_OUT)/system_other.img \
    $(PRODUCT_OUT)/installed-files.txt \
    $(PRODUCT_OUT)/installed-files-vendor.txt \
    $(PRODUCT_OUT)/installed-files-system-other.txt

关键设计:droid → droid_targets → droidcore,三层递进。droid_targets 是 droid 的实际工作层,droidcore 是核心聚合目标,把 systemimage、bootimage、userdataimage 等作为依赖。Make 在解析时发现这些依赖不存在,就会递归去构建它们。这就是"执行 make 就能生成所有镜像"的底层原理。

整个编译流程如下:

make (默认目标: droid)
│
├── droid_targets
│   └── droidcore(聚合目标)
    │
    ├── bootimage
    │   ├── 编译 kernel ← 内核源码
    │   ├── 生成 ramdisk ← 系统启动所需的 init、init.rc 等
    │   └── mkbootimg ← 打包成 boot.img
    │
    ├── systemimage
    │   ├── 编译所有 Java 代码  ← javac + jack
    │   ├── 编译所有 C/C++ 代码 ← clang / gcc
    │   ├── 编译所有资源文件    ← aapt
    │   ├── 收集所有模块产物到 system/ 目录
    │   └── make_ext4fs ← 生成 system.img
    │
    ├── userdataimage
    │   └── make_ext4fs ← 生成空的 userdata.img
    │
    └── recoveryimage
        └── mkbootimg ← 生成 recovery.img

五、关键源码文件索引

以下表格汇总了本篇涉及的核心源码文件:

文件作用
build/envsetup.sh注入 lunch、m、mm 等函数,环境初始化
build/core/main.mk编译入口,定义 droid 默认目标
build/core/Makefile各镜像的生成规则,system.img、boot.img 等
build/core/config.mk编译配置,路径、编译选项等
build/core/product.mk产品配置,继承关系
build/tools/releasetools/build_image.py镜像生成脚本,调用 make_ext4fs
system/core/libsparse/sparse image 相关工具源码
system/extras/ext4_utils/make_ext4fs 工具源码

六、本篇总结

本篇覆盖了从零搭建编译环境到完成首次全编的完整流程,核心要点:

  • 环境准备:Ubuntu 16.04 + OpenJDK 8 + 150GB 硬盘
  • 源码下载:Repo 工具 + 清华镜像站,repo init -b android-7.1.2_r36
  • 编译三部曲:envsetup.sh 注入函数 → lunch 选择目标 → make 触发 droidcore 构建所有镜像
  • 编译产物:最终在 out/target/product/<设备名>/ 下生成 boot.img、system.img、userdata.img 等

下一篇我们将深入 build/core/Makefile,逐行拆解 system.img 的生成规则——从 make_ext4fs 的调用到 sparse image 格式的转换,搞清楚一个文件夹到底是怎么变成 .img 文件的。


本系列基于 AOSP 7.1(Android Nougat)源码,代码路径以 AOSP 7.1 为准。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐