Android.mk与Android.bp介绍

一、 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 结构,极大缩短了 lunchmm 命令的等待时间。
  • 易于工具化维护 :结构化的数据使得像 androidmk 这样的转换工具能够自动将旧的 Android.mk 转换为 Android.bp,也便于 IDE 进行语法高亮和错误提示。

1.2 实际工程中的共存现状

虽然 Android.bp 代表了未来的方向,但在庞大的 AOSP 主干代码库以及众多芯片厂商(如高通、MTK)的闭源 HAL 层实现中,Android.mkAndroid.bp 的共存是一个长期存在的客观事实。

在实际工程中,这两种构建文件的协同工作逻辑如下:

  1. 编译入口的统一 :当执行 mmmm 命令时,构建系统会首先扫描目录树。若同时存在 Android.bpAndroid.mk,Soong 系统优先处理 Android.bp
  2. Kati 工具的过渡作用 :对于那些尚未转换或由于语法限制(例如包含复杂的条件判断、循环生成规则)无法直接转换为 Android.bpAndroid.mk 文件,系统会调用 Kati 工具。Kati 的作用是将 Android.mk 翻译为 Ninja 能理解的规则文件,从而与 Android.bp 生成的规则融合在一起。
  3. 模块间的互依赖 :无论是通过 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_FILESLOCAL_CFLAGS 等变量会残留到下一个模块的定义中。CLEAR_VARS 指向一个专门的清理脚本,它会重置除 LOCAL_PATH 以外的绝大多数 LOCAL_* 变量,确保每个模块定义的独立性。
  • LOCAL_MODULELOCAL_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_LIBRARIESLOCAL_SHARED_LIBRARIES 来管理模块间的依赖。

三种核心编译目标:

  1. 动态库(.so) :使用 include $(BUILD_SHARED_LIBRARY)
  2. 静态库(.a) :使用 include $(BUILD_STATIC_LIBRARY)
  3. 可执行文件 :使用 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/),可以在其同级目录显式列出需要包含的子目录:

    makefile 复制代码
    include $(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_sharedcc_binaryjava_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_sharedcc_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_dirsexport_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,但通过 targetarch 属性可实现针对不同平台、不同 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_libsldflags: ["-lxxx"]
LOCAL_MULTILIB compile_multilib
LOCAL_AIDL_INCLUDES aidl.include_dirs

3.6 实战:解读真实 AOSP 模块配置

以下代码节选自 frameworks/av/camera/Android.bp,展示了 cc_librarycc_library_headersfilegrouplicense 等模块的组合用法。

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_headerslibgui 的头文件传递出去。
  • 最终该模块可被动态链接或静态链接,完全由上层使用者决定。

四、 编译、部署与真机调试闭环

完成 Android.mkAndroid.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 查看完整编译命令行:

bash 复制代码
mmm 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 或先完成依赖项的转换。
相关推荐
zhangphil2 小时前
Android Coil3图片解码Bitmap后存入磁盘,再次加载读磁盘Bitmap缓存
android·kotlin
我命由我123452 小时前
Android 开发问题:SharedPreferences 的 getString 方法返回值类型 Type mismatch 问题
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
gjc5922 小时前
直击MySQL致命坑!GROUP_CONCAT默认截断不报错
android·数据库·mysql
Min_小明2 小时前
Android ANR 排查指南(思路、方法与实战案例)
android
chenjixue2 小时前
记录下我理解的安卓,鸿蒙,ios, rn , fullter, Jetpack Compose,react 的相似与不同
android·华为·harmonyos
REDcker2 小时前
Android ADB 命令教程与速查
android·adb
书中有颜如玉3 小时前
Kotlin Coroutines 异步编程实战:从原理到生产级应用
android·开发语言·kotlin
aq55356003 小时前
PHP7.2 vs 5.6:性能翻倍的关键升级
android
JJay.13 小时前
Android BLE 稳定连接的关键,不是扫描,而是 GATT 操作队列
android·服务器·前端