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

1.2 实际工程中的共存现状

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

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

  1. 编译入口的统一 :当执行 m 或 mmm 命令时,构建系统会首先扫描目录树。若同时存在 Android.bp 和 Android.mk,Soong 系统优先处理 Android.bp。
  2. Kati 工具的过渡作用 :对于那些尚未转换或由于语法限制(例如包含复杂的条件判断、循环生成规则)无法直接转换为 Android.bp 的 Android.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_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 来管理模块间的依赖。

三种核心编译目标:

  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_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 查看完整编译命令行:

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 或先完成依赖项的转换。
相关推荐
火柴就是我1 小时前
Flutter 项目本地 Maven 引入 AAR 找不到?一次 Gradle 路径问题排查
android
火柴就是我2 小时前
Android 打包报错 25.0.3
android·前端
码上有光2 小时前
Linux进程通信——共享内存、消息队列和信号量
android·linux·运维·共享内存·通信
Android打工仔6 小时前
Kotlin 协程源码解析:DispatchedContinuation 里的 Dispatcher 从哪里来?
android·kotlin
打工仔折腾 AI6 小时前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
Dovis(誓平步青云)7 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
恋猫de小郭11 小时前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter
执明wa11 小时前
Android RecyclerView 实战:点击频道栏切换对应内容
android·开发语言·windows·microsoft·android studio
新鲜势力呀11 小时前
PHP 定时数据同步实战:从第三方接口超时到异步同步 + 增量更新架构优化全过程
android
星间都市山脉13 小时前
RecoveryUI 圆角屏安全边距配置及代码调用链
android·java·linux·windows·ubuntu