Android 构建系统详解
深入理解 Android 构建系统是 Framework 开发的必备技能。本文详细讲解了 Android.mk 与 Android.bp 的语法、用户和迁移方案
一、构建系统的演变历史
1.1、Android 构建系统发展
| 版本 | 时间 | 构建系统 |
|---|---|---|
| Android 1-6 | 2008-2015 | 纯 Make (Android.mk) |
| Android 7 | 2016 | 引入 Soong (Android.bp),与 Make 并存 |
| Android 8 | 2017 | Soong 成为主要构建系统 |
| Android 10 | 2019 | 大部门模块迁移到 bp |
| Android 12 | 2021 | bp 为主,mk 逐渐被废弃 |
| Android 13+ | 2022+ | 引入 Bazel (实验性) |
1.2、为什么从 mk 迁移到 bp
Make 的问题
- ❌ 构建速度慢 - Make 是递归解析,效率低
- ❌ 难以并行 - 依赖关系复杂,并行度受限
- ❌ 语法复杂 - Makefile 语法晦涩,维护困难
- ❌ 调试困难 - 错误信息不友好
- ❌ 缺乏类型检查 - 容易出现拼写错误
Soong(bp)的优势
- ✓ 构建速度快 - 使用 Ninja 作为后端
- ✓ 高度并行 - 更好的依赖分析
- ✓ 语法简洁 - 类似 JSON,易读易写
- ✓ 类型安全 - 属性有类型检查
- ✓ 更好的模块化 - 更清晰的依赖管理
1.3、构建系统架构
二、Android.mk 详解
2.1、基本结构
makefile
# 每个 Android.mk 必须以此开头
LOCAL_PATH := $(callmy-dir)
# 清除之前的变量定义
include $(CLEAR_VARS)
# 模块名称
LOCAL_MODULE := my_module
# 源文件
LOCAL_SRC_FILES := \
src/file1.cpp \
src/file2.cpp
# 依赖的共享库
LOCAL_SHARED_LIBRARIES := \
liblog \
libutils
# 编译选项
LOCAL_CFLAGS := -Wall -Werror
# 构建目标类型
include $(BUILD_SHARED_LIBRARY)
2.2、核心变量讲解
| 变量 | 说明 |
|---|---|
| LOCAL_PATH | 当前目录路径,必须首先定义 |
| LOCAL_MODULE | 模块名称,唯一标识 |
| LOCAL_SRC_FILES | 源文件列表 |
| LOCAL_C_INCLUDES | C/C++ 头文件路径 |
| LOCAL_CFLAGS | C 编译选项 |
| LOCAL_SHARED_LIBRARIES | 依赖的动态库 |
| LOCAL_STATIC_LIBRARIES | 依赖的静态库 |
| LOCAL_CERTIFICATE | 签名证书:platform, shared, media |
| LOCAL_PRIVILEGED_MODULE | 是否为特权应用 |
2.3、构建目标类型
| 目标 | 说明 |
|---|---|
| BUILD_SHARED_LIBRARY | 编译成动态库(.so) |
| BUILD_STATIC_LIBRARY | 编译成静态库(.a) |
| BUILD_EXECUTABLE | 编译成可执行文件 |
| BUILD_PACKAGE | 编译为 APK |
| BUILD_JAVA_LIBRARY | 编译为 Java 库 (.jar) |
| BUILD_PREBUILT | 预编译模块 |
2.4、完整示例
示例1:编译动态库(.so)
makefile
LOCAL_PATH := $(callmy-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libmyutils
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := \
src/utils.cpp \
src/helper.cpp
LOCAL_C_INCLUDES := 、
$(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES := \
liblog \
libutils
LOCAL_CFLAGS := -Wall -Werror
LOCAL_CPPFLAGS := -std=c++17
include $(BUILD_SHARED_LIBRARY)
示例二:编译 APK 应用
make
LOCAL_PATH := $(callmy-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := MyApp
LOCAL_SRC_FILES := $(callall-java-files-under, src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_MANIFEST_FILE := AndroidManifest.xml
# 使用平台签名
LOCAL_CERTIFICATE := platform
# 放入 priv-app 目录
LOCAL_PRIVILGED_MODULE := true
# 使用平台私有 API
LOCAL_PRIVATE_PLATFORM_APIS := true
include $(BUILD_PACKAGE)
2.5、常用函数
| 函数 | 说明 |
|---|---|
| $(call my-dir) | 返回当前目录路径 |
| $(call all-java-files-under, dir) | 获取目录下所有 Java 文件 |
| $(call all-cpp-files-under, dir) | 获取目录下所有 C++ 文件 |
| $(wildcard pattern) | 通配符匹配文件 |
参考: https://blog.csdn.net/qq_45146250/article/details/134327011?spm=1001.2014.3001.5502
三、Android.bp 详解
3.1、基本语法
Android.bp 使用类似 JSON 的语法(Blueprint 语言),更简洁易读
makefile
// 模块定义
module_type {
name:"module_name",
property1:"value1",
property2:["list", "of", "values"],
property3:true,
nested_property: {
key: "value1",
}
}
3.2、核心模块类型
| 模块类型 | 说明 |
|---|---|
| C/C++模块 | |
| cc_library_shared | C/C++动态库(.so) |
| cc_library_static | C/C++静态库(.a) |
| cc_binary | C/C++可执行文件 |
| cc_library_headers | 仅头文件库 |
| Java 模块 | |
| java_library | Java库 |
| java_binary | Java可执行文件 |
Android 模块 android_app Android 应用(APK) android_library Android库(AAR) 其他 filegroup 文件分组 genrule 自定义生成规则
3.3、常用属性详解
| 属性 | 说明 |
|---|---|
| name | 模块名称(必需,全局唯一) |
| srcs | 源文件列表 |
| local_include_dirs | 本地头文件目录 |
| export_include_dirs | 导出的头文件目录 |
| shared_libs | 依赖的动态库 |
| static_libs | 依赖的静态库 |
| clfags | C 编译选项 |
| defaults | 继承的默认配置 |
| certificate | 签名证书(APK) |
| platform_apis | 使用平台 API(APK) |
| privileged | 特权应用(APK) |
3.4、完整示例
示例1:编译动态库 (cc_library_shared)
makefile
cc_library_shared {
name:"libmyutils",
srcs: [
"src/utils.cpp",
"src/helper.cpp",
],
local_include_dirs: ["include"],
export_include_dirs: ["include"],
shared_libs: [
"liblog",
"libutils",
"libbase",
],
cflags: ["-Wall","-Werror"],
cppflags: ["-std=c++17"],
vendor_available:true,
}
示例2:编译 Android 应用 (android_app)
makefile
android_app {
name:"MySystemApp",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
manifest:"AndroidManifest.xml",
// 使用平台签名
certificate:"platform",
// 特权应用
privileged:true,
// 使用平台私有 API
platform_apis:true,
static_libs: [
"androidx.appcompat_appcompat",
],
}
示例3:使用 defaults 简化配置
makefile
// 定义默认配置
cc_defaults{
name:"my_project_defaults",
cflags: ["-Wall","-Werror"],
shared_libs: ["liblog","libutils"],
}
// 使用默认配置
cc_library_shared{
name:"libmodule1",
defaults: ["my_project_defaults"],
srcs: ["module1.cpp"],
}
3.5、多条件配置
makefile
cc_library_shared {
name:"libconditional",
srcs: ["common.cpp"],
// 针对不同架构
arch: {
arm: {srcs: ["arm/*.cpp"] },
arm64: {srcs: ["arm64/*.cpp"] },
x86_64: {srcs: ["x86_64/*.cpp"] },
},
// 针对不同目标
target: {
android: {cflags: ["-DANDROID"] },
host: {cflags: ["-DHOST"] },
},
}
四、mk 与 bp 对比
4.1、语法对比
| Android.mk | Android.bp |
|---|---|
| LOCAL_PATH := $(call my-dir) | // 不需要,自动推断 |
| include $(CLEAR_VARS) | // 不需要 |
| LOCAL_MODULE := mylib | name: "mylib", |
| LOCAL_SRC_FILES := a.cpp b.cpp | srcs: ["a.cpp", "b.cpp"], |
| LOCAL_SHARED_LIBRARIES := liblog | shared_libs: ["liblog"], |
| LOCAL_CFLAGS := -Wall | cflags: ["-Wall"], |
| include $(BUILD_SHARED_LIBRARY) | cc_library_shared {...} |
4.2、变量名映射表
| Android.mk | Android.bp |
|---|---|
| LOCAL_MODULE | name |
| LOCAL_SRC_FILES | srcs |
| LOCAL_C_INCLUDES | local_include_dirs |
| LOCAL_EXPORT_C_INCLUDE_DIRS | export_include_dirs |
| LOCAL_SHARED_LIBRARIES | shared_libs |
| LOCAL_STATIC_LIBRARIES | static_libs |
| LOCAL_CERTIFICATE | certificate |
| LOCAL_PRIVILEGED_MODULE | privileged |
| LOCAL_PRIVATE_PLATFORM_APIS | platform_apis |
| LOCAL_INIT_RC | init_rc |
五、常用模块类型
5.1、C/C++ 模块
makefile
// 仅编译动态库
cc_library_shared{name:"libfoo_shared",srcs: ["foo.cpp"], }
// 仅编译静态库
cc_library_static{name:"libfoo_static",srcs: ["foo.cpp"], }
// 同时编译动态库和静态库
cc_library{name:"libfoo",srcs: ["foo.cpp"], }
// 仅头文件库
cc_library_headers{name:"libfoo_headers",export_include_dirs: ["include"], }
// 可执行文件
cc_binary{name:"foo_bin",srcs: ["main.cpp"], }
5.2、Android 应用模块
makefile
// 系统特权应用
android_app{
name:"MySystemApp",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
manifest:"AndroidManifest.xml",
platform_apis:true,
certificate:"platform",
privileged:true,
system_ext_specific:true,
}
5.3、预编译模块
makefile
// 预编译动态库
cc_prebuilt_library_shared{
name:"libprebuilt",
srcs: ["libs/libprebuilt.so"],
}
// 预编译 JAR
java_import{
name:"prebuilt-jar",
jars: ["libs/library.jar"],
}
// 预编译配置文件
prebuilt_etc{
name:"my_config",
src:"config/my.conf",
sub_dir:"myapp",
}
六、实例
案例一:添加新的系统服务
frameworks/base/services/core/java/.../myservice/ ├── Android.bp ├── MyService.java └── IMyService.aidl
makefile
java_library {
name: "services.myservice",
srcs: [
"java/**/*.java",
":myservice-aidl",
],
libs: ["services.core", "framework"],
}
filegroup {
name: "myservice-aidl",
srcs: ["aidl/**/*.aidl"],
}
案例二:添加新的 Native 服务
makefile
cc_binary {
name: "myservice",
srcs: ["MyService.cpp", "main.cpp"],
shared_libs: [
"libbase",
"liblog",
"libutils",
"libbinder",
],
init_rc: ["myservice.rc"],
vendor:true,
}
七、迁移指南
7.1、使用 androidmk 工具自动转换
makefile
# 编译 androidmk 工具
cd $AOSP_ROOT
source build/envsetup.sh
m androidmk
# 使用工具转换
androidmk Android.mk > Android.bp
# 工具位置
out/host/linux-x86/bin/androidmk
7.2、手动迁移注意事项
条件编译转换
mk: ifeq ($(TARGET_ARCH), arm64)
bp: arch: { arm64: { ... } }
不能转换的情况
• 复杂的条件逻辑
• 动态生成源文件列表
• 外部脚本调用 → 使用 genrule
八、调试技巧
8.1 常用调试命令
makefile
# 显示详细命令
m showcommands
# 单独编译某个模块,显示详细信息
m <module_name> showcommands 2>&1 | tee build.log
# 检查 bp 语法错误
out/soong/host/linux-x86/bin/bpfmt -d Android.bp
8.2 常见错误处理
| 错误 | 解决方案 |
|---|---|
| module 'xxx' not found | 检查依赖模块名是否正确 |
| property 'xxx' is not supported | 检查属性名是否正确 |
| duplicate module 'xxx' | 模块名全局唯一,检查重复 |
| unrecognized property | 属性名拼写错误或不支持 |
-
Android 12 以 Android.bp 为主,Android.mk 逐渐废弃
-
bp 语法类似 JSON,更简洁、更易维护
-
模块类型决定编译输出:
cc_library_shared → .so -
使用 defaults 复用配置,减少重复
-
条件编译使用
arch/target等属性块 -
可使用 androidmk 工具辅助迁移
记住这个对应关系
.so 库 |
→ cc_library_shared |
|---|---|
.a 库 |
→ cc_library_static |
可执行文件 |
→ cc_binary |
APK |
→ android_app |
JAR |
→ java_library |