一、 Android 构建系统演进概述
1.1 从 GNU Make 到 Soong
早期的 Android 开源项目(AOSP)采用了一套基于 GNU Make 的编译体系。开发者通过编写 Android.mk 文件来定义模块的编译规则,Make 工具读取这些文件并最终调用底层编译器(如 GCC 或 Clang)生成目标文件。这种设计在 Android 系统诞生之初具有极好的灵活性,但随着 AOSP 代码量呈指数级增长,纯粹基于 Make 的构建系统逐渐暴露出两个核心瓶颈:
第一,解析效率低下 。Android.mk 本质上是 Makefile 脚本片段,支持复杂的函数调用、条件判断和变量展开(例如 $(call my-dir)、$(foreach) 等)。在处理数千个模块时,Make 解析器需要反复展开变量、执行函数,导致单次全编的"空载"时间(no-op build time)过长。
第二,语法灵活导致维护成本高 。Makefile 的语法规则相对晦涩,允许开发者使用各种"黑魔法"手段实现依赖控制,不同芯片厂商或模块维护者写出的 Android.mk 风格迥异,难以通过工具进行静态分析和准确性校验。
为了解决这些痛点,Google 在 Android 7.0(Nougat)时期引入了全新的 Soong 构建系统 ,并随之推出了 Android.bp 配置文件。Soong 底层使用 Blueprint 框架和 Ninja 构建工具,其核心逻辑是将 配置与执行分离 :Android.bp 文件采用类似 JSON 的声明式数据结构,仅描述模块的静态属性(名称、源文件、依赖关系),不包含任何复杂的控制流逻辑。构建时,Soong 会高效地将所有 Android.bp 解析为底层的 build.ninja 文件,最后由 Ninja 执行极速的增量编译。
引入 Android.bp 的核心目的与优势如下:
- 结构化与强可读性 :它摒弃了 Makefile 的隐式规则,使用明确的键值对(Key-Value)格式。例如,定义动态库直接使用
cc_library_shared,而非难以记忆的include $(BUILD_SHARED_LIBRARY)宏。 - 极高的解析速度 :由于禁止了函数调用和动态变量展开,解析器可以快速遍历 JSON 结构,极大缩短了
lunch和mm命令的等待时间。 - 易于工具化维护 :结构化的数据使得像
androidmk这样的转换工具能够自动将旧的Android.mk转换为Android.bp,也便于 IDE 进行语法高亮和错误提示。
1.2 实际工程中的共存现状
虽然 Android.bp 代表了未来的方向,但在庞大的 AOSP 主干代码库以及众多芯片厂商(如高通、MTK)的闭源 HAL 层实现中,Android.mk 与 Android.bp 的共存是一个长期存在的客观事实。
在实际工程中,这两种构建文件的协同工作逻辑如下:
- 编译入口的统一 :当执行
m或mmm命令时,构建系统会首先扫描目录树。若同时存在Android.bp和Android.mk,Soong 系统优先处理Android.bp。 - Kati 工具的过渡作用 :对于那些尚未转换或由于语法限制(例如包含复杂的条件判断、循环生成规则)无法直接转换为
Android.bp的Android.mk文件,系统会调用 Kati 工具。Kati 的作用是将Android.mk翻译为 Ninja 能理解的规则文件,从而与Android.bp生成的规则融合在一起。 - 模块间的互依赖 :无论是通过
Android.bp中的static_libs声明的依赖,还是Android.mk中的LOCAL_STATIC_LIBRARIES声明的依赖,最终的产物(.a 或 .so)在链接阶段都是通用的。这意味着你完全可以在一个用Android.bp编写的cc_binary模块中,链接一个由旧版Android.mk编译出来的静态库。
二、 传统构建基石:Android.mk 核心解析
Android.mk 是 Android 构建系统中历史最悠久的模块配置文件,其本质是一个被构建系统包含的 Makefile 片段。理解它的运行逻辑,是分析 AOSP 旧版模块、适配芯片厂商闭源 HAL 层代码的基础。
2.1 基本文件结构与运行逻辑
一个标准的 Android.mk 文件通常以两个固定的宏定义开头,并在每一次定义新模块前执行一次"清理"操作。
示例:编译一个最简单的可执行文件
makefile
# 1. 固定开头:获取当前 Android.mk 所在的目录路径
LOCAL_PATH := $(call my-dir)
# 2. 清理变量:清除之前定义的所有 LOCAL_* 变量,避免污染当前模块
include $(CLEAR_VARS)
# 3. 模块定义区:定义当前模块的属性
LOCAL_MODULE := hello_test # 模块名称,也是生成的文件名
LOCAL_SRC_FILES := main.cpp helper.c # 源文件列表,支持空格或换行分隔
# 4. 编译目标声明:指定最终生成的文件类型
include $(BUILD_EXECUTABLE) # 生成可执行文件
运行逻辑详解:
LOCAL_PATH := $(call my-dir):这是每个Android.mk文件的第一行,my-dir是一个构建系统内置宏,它返回当前Android.mk所在的绝对路径。后续所有相对路径的引用(如源文件路径、头文件路径)都将基于LOCAL_PATH进行定位。include $(CLEAR_VARS):这个操作非常关键。由于 Make 语言的全局作用域特性,前一个模块定义的LOCAL_SRC_FILES、LOCAL_CFLAGS等变量会残留到下一个模块的定义中。CLEAR_VARS指向一个专门的清理脚本,它会重置除LOCAL_PATH以外的绝大多数LOCAL_*变量,确保每个模块定义的独立性。LOCAL_MODULE与LOCAL_SRC_FILES:前者定义模块的唯一名称(必须全局唯一),后者列出参与编译的源文件,多个文件以空格分隔,也可以使用反斜杠\进行换行书写以提高可读性。include $(BUILD_EXECUTABLE):这是整个编译流程的"触发器"。BUILD_EXECUTABLE指向系统预设的编译模板,它会读取前面定义的所有LOCAL_*变量,并执行编译、链接操作,最终在out/target/product/[设备名]/system/bin/下生成名为hello_test的可执行文件。
2.2 核心变量字典
Android.mk 的灵活性来源于丰富的 LOCAL_* 变量族,以下是最常用的几类。
| 变量名 | 作用 | 代码示例 |
|---|---|---|
LOCAL_MODULE |
模块名称,必须唯一 | LOCAL_MODULE := my_lib |
LOCAL_SRC_FILES |
源代码文件列表 | LOCAL_SRC_FILES := main.c util/helper.c |
LOCAL_C_INCLUDES |
头文件搜索路径(C/C++通用) | LOCAL_C_INCLUDES := $(LOCAL_PATH)/include |
LOCAL_CFLAGS |
C/C++ 编译器通用选项 | LOCAL_CFLAGS := -Wall -O2 -DDEBUG |
LOCAL_CPPFLAGS |
仅针对 C++ 编译器的选项 | LOCAL_CPPFLAGS := -std=c++17 -fno-exceptions |
LOCAL_LDLIBS |
链接系统动态库(如 Log、OpenGL) | LOCAL_LDLIBS := -llog -lGLESv2 |
LOCAL_STATIC_LIBRARIES |
依赖的静态库模块名(.a 文件) | LOCAL_STATIC_LIBRARIES := libutils_static |
LOCAL_SHARED_LIBRARIES |
依赖的动态库模块名(.so 文件) | LOCAL_SHARED_LIBRARIES := libcutils liblog |
代码示例:带自定义宏和系统库依赖的模块
makefile
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := advanced_module
LOCAL_SRC_FILES := main.cpp network/client.cpp
# 添加自定义宏和优化选项
LOCAL_CFLAGS := -Wall -Werror -O2
LOCAL_CPPFLAGS := -DENABLE_FEATURE_X=1
# 指定头文件查找目录(通常包含 vendor 或 external 下的自定义头文件)
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/include \
$(LOCAL_PATH)/../third_party/jsoncpp/include
# 链接系统日志库和数学库
LOCAL_LDLIBS := -llog -lm
include $(BUILD_SHARED_LIBRARY)
2.3 目标生成规则与依赖管理
Android.mk 通过引用不同的 BUILD_* 宏来决定最终输出产物的格式,并通过 LOCAL_STATIC_LIBRARIES 和 LOCAL_SHARED_LIBRARIES 来管理模块间的依赖。
三种核心编译目标:
- 动态库(.so) :使用
include $(BUILD_SHARED_LIBRARY)。 - 静态库(.a) :使用
include $(BUILD_STATIC_LIBRARY)。 - 可执行文件 :使用
include $(BUILD_EXECUTABLE)。
依赖管理实战:可执行文件链接自定义静态库与动态库
假设项目目录结构如下:
text
project/
├── libs/
│ ├── static_util/
│ │ ├── util.cpp
│ │ └── Android.mk
│ └── shared_engine/
│ ├── engine.cpp
│ └── Android.mk
└── app/
├── main.cpp
└── Android.mk
步骤一:编译静态库 (libs/static_util/Android.mk)
makefile
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libstatic_util
LOCAL_SRC_FILES := util.cpp
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) # 导出头文件路径给依赖者
include $(BUILD_STATIC_LIBRARY)
步骤二:编译动态库 (libs/shared_engine/Android.mk)
makefile
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libshared_engine
LOCAL_SRC_FILES := engine.cpp
LOCAL_STATIC_LIBRARIES := libstatic_util # 动态库内部可以链接静态库
include $(BUILD_SHARED_LIBRARY)
步骤三:编译可执行文件并链接上述库 (app/Android.mk)
makefile
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := my_app
LOCAL_SRC_FILES := main.cpp
# 依赖静态库(代码会直接链接进可执行文件)
LOCAL_STATIC_LIBRARIES := libstatic_util
# 依赖动态库(运行时需要 .so 文件存在)
LOCAL_SHARED_LIBRARIES := libshared_engine
# 系统库依赖
LOCAL_LDLIBS := -llog
include $(BUILD_EXECUTABLE)
注意 :静态库会被直接打包进最终的可执行文件或动态库中,因此不存在运行时查找路径的问题;而动态库则需要在设备刷机或推送时确保存在于
/system/lib(或lib64)目录中,否则运行时会提示CANNOT LINK EXECUTABLE错误。
2.4 多目录与复杂工程组织
对于包含数十个甚至上百个子模块的大型项目,手动进入每个子目录执行 mm 显然不现实。Android 构建系统提供了自动遍历子目录的机制。
顶层 Android.mk 写法:
在项目的根目录下创建一个 Android.mk,内容仅包含一行包含指令:
makefile
# project/Android.mk
include $(call all-subdir-makefiles)
工作原理:
all-subdir-makefiles是构建系统提供的一个函数宏,它会递归遍历当前目录下的所有一级子目录,并收集其中存在的Android.mk文件路径。- 当你在 AOSP 源码根目录下执行
make或在项目根目录下执行mm时,构建系统会通过这一行指令一次性加载该目录树下的所有模块定义,从而实现整个项目的批量编译。
实战技巧:
-
选择性包含 :如果不想编译某个子目录(例如测试代码目录
test/),可以在其同级目录显式列出需要包含的子目录:makefileinclude $(LOCAL_PATH)/src/Android.mk include $(LOCAL_PATH)/libs/Android.mk # 不包含 test/ 目录 -
与源码树集成 :在 AOSP 开发中,通常将自定义项目放置在
vendor/[厂商名]/[项目名]/或device/[厂商名]/[产品名]/目录下,并通过PRODUCT_PACKAGES变量将生成的模块添加至系统镜像中,这已超出单模块Android.mk范畴,但理解all-subdir-makefiles是理解整个系统编译链条的关键一环。
三、 现代构建标准:Android.bp 语法解剖
随着 Android 构建系统向 Soong 迁移,Android.bp 逐渐成为定义模块的主流方式。与 Android.mk 的过程式描述不同,Android.bp 采用了一种声明式的、类 JSON 的结构化语言,称为 Blueprint。它的核心思想是"描述目标是什么",而非"描述如何构建目标"。
3.1 Blueprint 语法基础
Blueprint 语法极其简洁,仅包含几种基本数据类型和单一的模块定义结构,去除了所有控制流语句(如条件判断、循环),这使得解析器能够高速运行,也迫使开发者编写出清晰、无歧义的构建描述。
基本模块结构
blueprint
模块类型 {
属性名1: "字符串值",
属性名2: ["列表项1", "列表项2"],
属性名3: true,
}
- 模块类型 :由构建系统预定义的构建规则标识符,例如
cc_library_shared、cc_binary、java_library等。 - 属性名与值:使用冒号分隔,值类型支持字符串、字符串列表、布尔值以及嵌套对象(较少见)。
- 注释 :支持 C/C++ 风格的单行注释
//和多行注释/* */。
数据类型示例
blueprint
cc_binary {
// 字符串:模块的唯一标识
name: "my_tool",
// 字符串列表:源文件、依赖库、编译标志等
srcs: [
"main.cpp",
"utils/parser.cpp",
"utils/logger.cpp",
],
// 布尔值:控制开关
enabled: true,
// 也可以混合单字符串和列表(当只有一个元素时,二者等价)
include_dirs: "include", // 等同于 ["include"]
shared_libs: ["liblog", "libcutils"],
}
3.2 核心模块类型总览
Android.bp 针对不同场景提供了丰富的模块类型。根据产物类型和链接方式,可将它们分为基础产物类型 与灵活派生类型两大类。
3.2.1 基础产物类型(明确指定输出格式)
| 模块类型 | 产物 | 对应 Android.mk 宏 |
|---|---|---|
cc_library_shared |
动态库(.so) |
include $(BUILD_SHARED_LIBRARY) |
cc_library_static |
静态库(.a) |
include $(BUILD_STATIC_LIBRARY) |
cc_binary |
可执行文件 | include $(BUILD_EXECUTABLE) |
cc_library_host_static |
主机端静态库 | --- |
cc_binary_host |
主机端可执行文件 | --- |
android_app |
APK 应用 | include $(BUILD_PACKAGE) |
代码示例:cc_library_shared
blueprint
cc_library_shared {
name: "libmy_engine",
srcs: ["engine.cpp", "decoder.cpp"],
shared_libs: ["liblog", "libutils"],
static_libs: ["libtinyxml2"],
export_include_dirs: ["include"],
cflags: ["-Wall", "-O2"],
}
3.2.2 灵活派生类型(由依赖方决定输出格式)
在实际 AOSP 源码中,cc_library 远比 cc_library_shared 和 cc_library_static 更常见。它是一类通用库模块 ,本身不预设输出格式,最终产物类型由依赖它的其他模块决定:
- 当通过
shared_libs: ["libxxx"]依赖时 → 编译为动态库。 - 当通过
static_libs: ["libxxx"]依赖时 → 编译为静态库。 - 若同时被两种方式依赖 → 同时生成两套产物。
为何 AOSP 偏爱 cc_library?
以 frameworks/av/camera/Android.bp 中的 libcamera_client 为例:
- 相机服务进程(
cameraserver)需要动态链接,节省内存; - 某些测试工具或 HAL 实现希望静态链接,避免运行时依赖。
使用 cc_library 可让同一份源码适应多种链接场景,极大提升了模块复用性。
代码示例:cc_library
blueprint
cc_library {
name: "libcamera_client",
srcs: [
"Camera.cpp",
"CameraMetadata.cpp",
":libcamera_client_aidl", // 引用 filegroup
],
shared_libs: [
"libbase",
"libbinder",
"libgui",
],
export_include_dirs: ["include"],
cflags: ["-Werror", "-Wall"],
}
3.2.3 辅助类型(不产生编译产物)
| 模块类型 | 作用 |
|---|---|
cc_library_headers |
头文件库,仅用于导出头文件路径,无实际编译产物。 |
filegroup |
文件集合,用于将一组文件打包为一个逻辑单元供其他模块引用。 |
license |
声明模块的许可证信息,满足开源合规要求。 |
代码示例:cc_library_headers 与 filegroup
blueprint
// 头文件库:任何依赖本模块的目标都会自动获得 include 目录
cc_library_headers {
name: "camera_headers",
export_include_dirs: ["include"],
}
// 文件组:将 AIDL 文件集中管理
filegroup {
name: "libcamera_client_aidl",
srcs: [
"aidl/android/hardware/ICameraService.aidl",
"aidl/android/hardware/camera2/ICameraDeviceUser.aidl",
],
path: "aidl",
}
3.3 关键属性全解析
下表汇总了 Android.bp 中与 C/C++ 编译最密切的属性,并特别补充了在真实 AOSP 模块中频繁出现的属性。
| 属性名 | 类型 | 作用与说明 |
|---|---|---|
name |
string | 模块唯一名称,全局不可重复。 |
srcs |
list | 源文件列表,支持通配符和 :module_name 引用 filegroup。 |
shared_libs |
list | 依赖的动态库模块名称列表。 |
static_libs |
list | 依赖的静态库模块名称列表。 |
header_libs |
list | 依赖的头文件库 (cc_library_headers),仅引入头文件路径。 |
export_include_dirs |
list | 导出头文件目录。依赖本模块的目标会自动加入这些目录。 |
local_include_dirs |
list | 私有头文件目录,仅本模块内部可见。 |
include_dirs |
list | (不推荐)同时设置导出和私有目录,优先使用上述两个属性。 |
cflags |
list | C/C++ 编译器通用标志。 |
cppflags |
list | 仅 C++ 编译器标志。 |
ldflags |
list | 链接器标志。 |
export_shared_lib_headers |
list | 将所依赖动态库的头文件路径传递给依赖本模块的目标。 |
aidl |
object | AIDL 编译配置(含 local_include_dirs、export_aidl_headers 等)。 |
compile_multilib |
string | 编译位数:"both"(默认)、"32"、"64"、"first"。 |
host_supported |
bool | 是否支持在主机端编译(用于 cc_library 等)。 |
vendor |
bool | 是否编译到 vendor 分区。 |
visibility |
list | 模块可见性控制,例如 ["//visibility:public"]。 |
高级属性示例:aidl 与 export_shared_lib_headers
blueprint
cc_library {
name: "libcamera_client",
aidl: {
export_aidl_headers: true,
local_include_dirs: ["aidl"],
include_dirs: ["frameworks/native/aidl/gui"],
},
// 将 libgui 等动态库的头文件传递给依赖者
export_shared_lib_headers: ["libgui", "libbinder"],
}
3.4 条件编译与架构变体
Blueprint 本身不支持 if/else,但通过 target 和 arch 属性可实现针对不同平台、不同 CPU 架构的条件编译。
blueprint
cc_library {
name: "libarch_dependent",
arch: {
arm: {
cflags: ["-DARM_SPECIFIC"],
},
arm64: {
cflags: ["-DARM64_SPECIFIC"],
},
x86: {
cflags: ["-DX86_SPECIFIC"],
},
},
target: {
android: {
shared_libs: ["libcutils"],
},
linux: {
shared_libs: ["libpthread"],
},
},
}
3.5 Android.mk 到 Android.bp 快速对照
| Android.mk 变量 | Android.bp 属性 |
|---|---|
LOCAL_MODULE |
name |
LOCAL_SRC_FILES |
srcs |
LOCAL_SHARED_LIBRARIES |
shared_libs |
LOCAL_STATIC_LIBRARIES |
static_libs |
LOCAL_C_INCLUDES |
local_include_dirs + export_include_dirs |
LOCAL_EXPORT_C_INCLUDE_DIRS |
export_include_dirs |
LOCAL_CFLAGS |
cflags |
LOCAL_CPPFLAGS |
cppflags |
LOCAL_LDFLAGS |
ldflags |
LOCAL_LDLIBS |
shared_libs 或 ldflags: ["-lxxx"] |
LOCAL_MULTILIB |
compile_multilib |
LOCAL_AIDL_INCLUDES |
aidl.include_dirs |
3.6 实战:解读真实 AOSP 模块配置
以下代码节选自 frameworks/av/camera/Android.bp,展示了 cc_library、cc_library_headers、filegroup、license 等模块的组合用法。
blueprint
package {
default_applicable_licenses: ["frameworks_av_camera_license"],
}
license {
name: "frameworks_av_camera_license",
visibility: [":__subpackages__"],
license_kinds: ["SPDX-license-identifier-Apache-2.0"],
license_text: ["NOTICE"],
}
cc_library_headers {
name: "camera_headers",
export_include_dirs: ["include"],
}
cc_library {
name: "libcamera_client",
aidl: {
export_aidl_headers: true,
local_include_dirs: ["aidl"],
},
srcs: [
":libcamera_client_aidl",
"Camera.cpp",
"CameraMetadata.cpp",
],
shared_libs: [
"libbase",
"libbinder",
"libgui",
],
export_include_dirs: ["include"],
export_shared_lib_headers: ["libgui"],
cflags: ["-Werror", "-Wall"],
}
filegroup {
name: "libcamera_client_aidl",
srcs: [
"aidl/android/hardware/ICameraService.aidl",
"aidl/android/hardware/camera2/ICameraDeviceUser.aidl",
],
path: "aidl",
}
模块关系解析:
camera_headers作为纯头文件库,任何依赖它的模块都能直接使用 camera 公共头文件。libcamera_client通过srcs: [":libcamera_client_aidl"]引用 AIDL 文件组,实现源文件解耦。- 通过
shared_libs声明运行时依赖,通过export_shared_lib_headers将libgui的头文件传递出去。 - 最终该模块可被动态链接或静态链接,完全由上层使用者决定。
四、 编译、部署与真机调试闭环
完成 Android.mk 或 Android.bp 的编写后,需要将源码编译为可执行文件或库,并部署到真实设备或模拟器上进行验证。本节将完整覆盖从编译到真机运行的每一个环节。
4.1 编译环境初始化
在编译任何 Android 模块之前,必须正确初始化构建环境。
bash
# 进入 AOSP 源码根目录
cd /path/to/aosp
# 加载编译环境变量和辅助函数
source build/envsetup.sh
# 选择目标设备配置(如 aosp_arm64-eng 或具体产品名)
lunch aosp_arm64-eng
envsetup.sh 脚本会向当前 Shell 注入大量实用命令,包括:
m:全量编译整个系统。mm:编译当前目录及其依赖模块。mmm:编译指定目录下的模块。cgrep/jgrep:代码搜索工具。
4.2 模块化编译指令
对于单模块或小范围模块的编译,使用 mmm 最为高效。
| 命令 | 作用 | 示例 |
|---|---|---|
mmm <目录> |
编译指定目录下的所有模块 | mmm external/project/executable |
mm |
编译当前所在目录的模块(需先 cd 到模块目录) |
cd external/project && mm |
mma |
编译当前目录及其所有依赖 | mma |
编译静态库示例:
bash
mmm external/project/static_lib
编译成功后,产物默认位于:
text
out/target/product/<设备名>/obj/STATIC_LIBRARIES/libhello_static_intermediates/libhello_static.a
编译动态库示例:
bash
mmm external/project/shared_lib
产物位于:
text
out/target/product/<设备名>/system/lib64/libhello_shared.so # 64位设备
out/target/product/<设备名>/system/lib/libhello_shared.so # 32位设备
编译可执行文件示例:
bash
mmm external/project/executable
产物位于:
text
out/target/product/<设备名>/system/bin/hello_executable
提示 :若编译失败,可添加
-j<N>参数限制并行任务数以获取清晰错误日志,或使用showcommands查看完整编译命令行:
bashmmm external/project/executable -j1 showcommands
4.3 产物推送至设备
编译完成后,需将产物推送到 Android 设备中运行。以下为 adb 命令的标准操作流程。
① 确认设备连接并获取 root 权限
bash
adb devices # 列出已连接设备
adb root # 以 root 身份重启 adbd(系统分区需要 root 写权限)
adb remount # 重新挂载系统分区为可读写(仅 userdebug/eng 版本有效)
② 推送可执行文件和动态库
bash
# 推送可执行文件
adb push out/target/product/<设备名>/system/bin/hello_executable /system/bin/
# 推送依赖的动态库(32位设备用 lib,64位设备用 lib64)
adb push out/target/product/<设备名>/system/lib64/libhello_shared.so /system/lib64/
③ 赋予可执行权限
bash
adb shell chmod 755 /system/bin/hello_executable
# 或使用 chmod +x
adb shell chmod +x /system/bin/hello_executable
④ 运行程序并观察输出
bash
adb shell /system/bin/hello_executable
期望输出:
text
Hello from static library!
Hello from shared library!
4.4 调试与问题定位
在真机运行阶段,常见问题可分为以下几类,每类均有对应的排查手段。
| 现象 | 可能原因 | 排查命令 |
|---|---|---|
CANNOT LINK EXECUTABLE |
依赖的动态库缺失或路径不匹配 | adb shell ls -l /system/lib64/libhello_shared.so |
Permission denied |
未赋予可执行权限 | adb shell chmod +x /system/bin/hello_executable |
No such file or directory |
动态库链接器路径不匹配 | adb shell "LD_LIBRARY_PATH=/system/lib64 /system/bin/hello_executable" |
| 运行时崩溃(SIGSEGV) | 内存错误或 ABI 不匹配 | `adb shell logcat |
4.5 Android.mk 到 Android.bp 的自动转换工具
Google 提供了一个名为 androidmk 的命令行工具,用于将旧版 Android.mk 转换为 Android.bp。该工具位于 AOSP 源码树中,可在编译环境初始化后直接使用。
使用方法:
bash
androidmk Android.mk > Android.bp
转换示例:
输入 Android.mk:
makefile
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libtest
LOCAL_SRC_FILES := test.c
LOCAL_SHARED_LIBRARIES := liblog
include $(BUILD_SHARED_LIBRARY)
输出 Android.bp:
blueprint
cc_library_shared {
name: "libtest",
srcs: ["test.c"],
shared_libs: ["liblog"],
}
注意事项:
androidmk无法完美转换包含复杂 Make 函数(如$(foreach)、条件判断)的Android.mk,这类文件需要人工重构。- 转换后建议使用
bpfmt -w Android.bp对文件进行格式化。 - 若模块依赖了尚未转换为
Android.bp的其他模块,需保留Android.mk或先完成依赖项的转换。