
Android.mk 文件是 Android 平台上的 Makefile 文件,用于定义 Android 应用或库中模块的编译方式。它是 Android NDK(Native Development Kit)的一部分,允许开发者使用 C 或 C++ 等原生语言来编写应用或库的某些部分。本文将简要介绍如何入门学习 Android.mk 文件的编写。
Android.mk 文件是 Android NDK(Native Development Kit)构建系统中的一个核心组成部分,用于定义如何在 Android 项目中编译和链接原生代码(主要是 C/C++)。一个基本的 Android.mk 文件结构通常包含以下几个部分:
LOCAL_MODULE 变量定义模块的名称。
LOCAL_SRC_FILES 变量列出所有要编译的源文件(C/C++ 源文件、头文件等,但通常只列出源文件)。
LOCAL_LDLIBS(链接系统库)或 LOCAL_SHARED_LIBRARIES/LOCAL_STATIC_LIBRARIES(链接自定义的共享库/静态库)来指定这些依赖。
LOCAL_CFLAGS 用于 C/C++ 编译器标志,LOCAL_LDFLAGS 用于链接器标志)来定制编译过程。
下面是一个简单的 Android.mk 文件示例,它定义了一个名为 hello-jni 的共享库模块:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 定义模块名称
LOCAL_MODULE := hello-jni
# 定义模块类型为共享库
include $(BUILD_SHARED_LIBRARY)
# 如果有源文件,需要这样指定
# LOCAL_SRC_FILES := hello-jni.c
# 如果需要链接系统库,可以这样指定(例如,如果使用了日志功能)
# LOCAL_LDLIBS += -llog
# 如果依赖其他自定义的库,可以这样指定
# LOCAL_SHARED_LIBRARIES := libdependency请注意,上面的示例中并没有显式指定源文件列表(LOCAL_SRC_FILES),因为这是一个非常简单的示例,只展示了如何定义和指定模块类型。在实际的项目中,需要根据项目的需求来设置这些变量。
此外,LOCAL_PATH := $(call my-dir) 这行代码非常重要,它用于获取当前 Android.mk 文件所在的目录路径,并存储在 LOCAL_PATH 变量中,以便在后续的 include 指令中使用相对路径来引用其他文件或 Android.mk 文件。
在 Android 的 NDK 构建系统中,Android.mk 文件通过包含不同的 GNU Makefile 片段(即预定义的变量和规则)来指定模块的编译类型。这些片段定义了如何将源文件编译成不同的输出类型,如共享库、静态库或可执行文件。
BUILD_SHARED_LIBRARY。这会在你的 Android.mk 文件中创建一个 .so(Shared Object)文件。include $(CLEAR_VARS)
LOCAL_MODULE := my_shared_library
LOCAL_SRC_FILES := my_source_file.c
include $(BUILD_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_MODULE := my_static_library
LOCAL_SRC_FILES := my_source_file.c
include $(BUILD_STATIC_LIBRARY)注意:静态库通常不会单独构建为目标,而是作为其他模块(如共享库或可执行文件)的依赖项来构建。
BUILD_EXECUTABLE。然而,在 Android 应用开发中,更常见的是创建 Java/Kotlin 应用程序,并使用 JNI(Java Native Interface)来调用本地(C/C++)代码。include $(CLEAR_VARS)
LOCAL_MODULE := my_executable
LOCAL_SRC_FILES := my_main_function.c
include $(BUILD_EXECUTABLE)但是,请注意,直接在 Android 设备上运行可执行文件可能会受到安全限制和沙箱机制的约束。
include $(CLEAR_VARS) 来清除之前设置的所有局部变量(如 LOCAL_MODULE、LOCAL_SRC_FILES 等),这是必要的,因为这些变量是全局的,并且会影响后续的模块定义。LOCAL_SHARED_LIBRARIES 或 LOCAL_STATIC_LIBRARIES 中指定这些依赖项。Android.mk 文件,并根据这些文件中定义的规则来编译和链接你的模块。在 Android.mk 文件中,变量用于存储配置信息,如源文件列表、模块名称、编译选项等。变量赋值使用 := 操作符,例如:
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c这里,LOCAL_MODULE 被赋值为 hello-jni,LOCAL_SRC_FILES 被赋值为 hello-jni.c。
注意:使用 := 进行赋值时,如果右侧的值中引用了其他变量,这些变量会被立即展开。如果想要延迟展开(即,在变量实际被使用时才展开),可以使用 = 赋值符,但这在 Android.mk 文件中不太常见。
在 Android NDK 的 Android.mk 文件中,include $(CLEAR_VARS) 是一个非常重要的步骤,用于在定义新的模块之前清除之前设置的所有局部变量。这是必要的,因为 Android.mk 文件中定义的变量(如 LOCAL_MODULE、LOCAL_SRC_FILES 等)是全局的,并且在同一个 Makefile 解析过程中持续存在。
如果没有清除这些变量,那么后续定义的模块可能会意外地继承之前模块的设置,导致构建错误或生成不正确的输出。
CLEAR_VARS 是一个由 NDK 提供的特殊变量,它指向一个 GNU Makefile 片段,该片段定义了一系列空值来重置之前可能设置的任何局部变量。因此,通过在定义新模块之前包含 $(CLEAR_VARS),可以确保每个模块都从一个干净的状态开始。
下面是一个典型的 Android.mk 文件片段,展示了如何使用 include $(CLEAR_VARS) 来定义多个模块:
LOCAL_PATH := $(call my-dir)
# 清除之前的变量
include $(CLEAR_VARS)
# 定义第一个模块
LOCAL_MODULE := first_module
LOCAL_SRC_FILES := first_module.c
include $(BUILD_SHARED_LIBRARY)
# 再次清除变量,为下一个模块做准备
include $(CLEAR_VARS)
# 定义第二个模块
LOCAL_MODULE := second_module
LOCAL_SRC_FILES := second_module.c
include $(BUILD_STATIC_LIBRARY)
# ... 可以继续定义更多的模块在这个例子中,first_module 和 second_module 是两个完全独立的模块,它们分别被编译为共享库和静态库。通过在每个模块定义之前使用 include $(CLEAR_VARS),我们确保了它们的配置不会相互干扰。
在 Android NDK 的 Android.mk 文件中,如果模块需要链接其他库(无论是静态库、共享库还是系统库),可以通过不同的方式来指定这些依赖。
如果模块需要链接其他自定义的静态库或共享库,应该使用 LOCAL_STATIC_LIBRARIES 或 LOCAL_SHARED_LIBRARIES 变量来指定这些库的名称。这些变量告诉构建系统在链接你的模块时需要包含哪些库。
LOCAL_STATIC_LIBRARIES:指定要链接的静态库(.a 文件)。LOCAL_SHARED_LIBRARIES:指定要链接的共享库(.so 文件)。例如:
include $(CLEAR_VARS)
LOCAL_MODULE := my_module
LOCAL_SRC_FILES := my_source.c
LOCAL_STATIC_LIBRARIES := libstatic # 链接名为 libstatic 的静态库
LOCAL_SHARED_LIBRARIES := libshared # 链接名为 libshared 的共享库
include $(BUILD_SHARED_LIBRARY)
# 假设已经有 libstatic.a 和 libshared.so 的定义在其他 Android.mk 文件中
# 或者它们是由你的项目中的其他模块生成的注意:在链接自定义库时,确保这些库已经在项目中被正确定义,并且它们的构建顺序要早于模块,或者它们已经以某种方式被包含在构建路径中。
如果需要链接 Android 系统提供的库(如 libc、libm、liblog 等),应该使用 LOCAL_LDLIBS 变量来指定这些库的名称。但是,请注意,对于大多数系统库,通常不需要直接指定它们,因为 NDK 的工具链已经默认链接了这些库。然而,对于某些不是默认链接的库(如 liblog),可以这样做:
include $(CLEAR_VARS)
LOCAL_MODULE := my_module
LOCAL_SRC_FILES := my_source.c
LOCAL_LDLIBS += -llog # 链接 Android 的日志库
include $(BUILD_SHARED_LIBRARY)在这个例子中,-llog 告诉链接器要链接 Android 的日志库(liblog.so)。注意,LOCAL_LDLIBS 中的条目前面通常会有 -l 前缀(表示链接库),并且如果库名不是标准的(即不以 lib 开头),则可能需要完整指定库名(但在这个例子中,log 是标准的 Android 系统库名)。
Android.mk 文件中使用 include 语句来包含定义这些库的其他 Android.mk 文件,或者确保这些库是通过其他方式(如预构建库)被包含在你的项目中的。LOCAL_LDFLAGS 变量来指定额外的库搜索路径(使用 -L 选项)。在 Android NDK 的 Android.mk 文件中,实际上并不直接支持 GNU Make 的条件语句(如 ifeq、ifneq、else、endif)来进行条件编译,因为 Android.mk 文件主要是被 Android 的构建系统(基于 soong 和 kati)用来解析和转换成 Ninja 构建文件的。然而,可以通过一些间接的方法来实现类似条件编译的效果。
一种常见的做法是使用环境变量来控制编译过程。可以在构建时设置环境变量,然后在 Android.mk 文件中检查这些变量来决定是否包含某些源文件或编译选项。虽然这不是直接在 Android.mk 文件中使用条件语句,但它可以达到类似的效果。
例如,可以在命令行中设置环境变量:
export MY_FEATURE_ENABLED=1
ndk-build然后在 Android.mk 文件中检查这个环境变量:
include $(CLEAR_VARS)
LOCAL_MODULE := my_module
# 检查环境变量
ifneq ($(MY_FEATURE_ENABLED),)
LOCAL_SRC_FILES += feature_enabled.c
endif
LOCAL_SRC_FILES += main.c
include $(BUILD_SHARED_LIBRARY)另一种方法是使用自定义变量和包含额外的 .mk 文件。可以根据条件包含不同的 .mk 文件,这些文件定义了不同的源文件列表或编译选项。
例如,可以有两个 .mk 文件:config_feature_enabled.mk 和 config_feature_disabled.mk,然后在 Android.mk 中根据条件包含其中一个:
include $(CLEAR_VARS)
LOCAL_MODULE := my_module
# 假设有一个变量或环境变量来控制功能是否启用
ifeq ($(ENABLE_MY_FEATURE),1)
include $(LOCAL_PATH)/config_feature_enabled.mk
else
include $(LOCAL_PATH)/config_feature_disabled.mk
endif
LOCAL_SRC_FILES += main.c
# config_*.mk 文件将可能添加额外的源文件
include $(BUILD_SHARED_LIBRARY)Android.mk 文件的这些限制,如果需要更复杂的条件逻辑,可能需要考虑使用 Android 的其他构建机制,如 CMake 或直接在构建脚本中处理这些逻辑。Android.mk 文件中使用 GNU Make 的条件语句(如 ifeq)通常不会按预期工作,因为 Android.mk 文件不是直接由 GNU Make 解析的。相反,它们被 Android 的构建系统解析并转换为 Ninja 构建文件或其他内部表示。可以设置多种编译选项来定制模块构建过程。这些选项包括 C/C++ 的包含路径、编译器标志等
LOCAL_C_INCLUDES 变量用于指定额外的 C/C++ 包含(头文件)路径。这些路径会被添加到 C/C++ 编译器的包含搜索路径中,使得编译器能够找到你的项目中使用的头文件。
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_C_INCLUDES += /path/to/another/include在这个例子中,(LOCAL_PATH)/include 和 /path/to/another/include 将会被添加到包含路径中。(LOCAL_PATH) 是一个自动变量,它代表了当前 Android.mk 文件所在的目录的路径。
LOCAL_CFLAGS 变量用于指定 C 编译器的标志。这些标志会在编译 C 源文件时传递给 C 编译器。可以使用这些标志来启用或禁用特定的编译器功能,如优化选项、警告级别等。
OCAL_CFLAGS += -Wall -Wextra -O2 在这个例子中,-Wall 和 -Wextra 将会开启额外的警告信息,而 -O2 将会启用二级优化。
LOCAL_CPPFLAGS 变量与 LOCAL_CFLAGS 类似,但它专门用于指定 C++ 编译器的标志。这些标志会在编译 C++ 源文件时传递给 C++ 编译器。如果需要为 C++ 源文件设置特定的编译器标志(而 C 源文件不需要这些标志),则应该使用 LOCAL_CPPFLAGS 而不是 LOCAL_CFLAGS。
LOCAL_CPPFLAGS += -std=c++11 -Wall -Wextra -O2 在这个例子中,-std=c++11 指定了使用 C++11 标准,-Wall、-Wextra 和 -O2 的作用与前面相同。
LOCAL_CFLAGS 和 LOCAL_CPPFLAGS 中,但通常将它们放在 LOCAL_CFLAGS 中就足够了,因为 C++ 编译器通常也接受 C 编译器的标志。然而,对于特定于 C++ 的标志(如 -std=c++11),你应该将它们放在 LOCAL_CPPFLAGS 中。LOCAL_LDFLAGS 变量来指定链接器标志,这些标志会在链接阶段传递给链接器。.mk 文件中,并在需要时包含它。LOCAL_LDLIBS 变量用于指定链接器(linker)在链接模块时需要额外考虑的库。这些库可以是系统库,也可以是项目中其他模块生成的库(尽管对于后者,通常更推荐使用 LOCAL_SHARED_LIBRARIES 或 LOCAL_STATIC_LIBRARIES)。
当需要链接系统库时,LOCAL_LDLIBS 变得非常有用。例如,如果想要链接 Android 的日志库(liblog),可以在 Android.mk 文件中这样设置:
LOCAL_LDLIBS += -llog 这里的 -llog 告诉链接器在链接模块时,需要链接到名为 liblog 的库。注意,在链接系统库时,通常不需要指定库文件的前缀(如 lib)和后缀(如 .so),只需要指定库的基本名称即可。
然而,需要注意的是,对于大多数 Android NDK 提供的系统库,通常不需要通过 LOCAL_LDLIBS 来手动链接它们,因为 NDK 的构建系统已经处理了这些依赖。LOCAL_LDLIBS 更多地用于链接那些不是由 NDK 直接管理的库,或者当有特殊的链接需求时。
另外,如果需要链接自己项目中其他模块生成的库(无论是静态库还是共享库),应该使用 LOCAL_STATIC_LIBRARIES 或 LOCAL_SHARED_LIBRARIES 而不是 LOCAL_LDLIBS。这是因为 LOCAL_STATIC_LIBRARIES 和 LOCAL_SHARED_LIBRARIES 提供了更明确的依赖关系管理,它们允许 NDK 构建系统正确地处理库之间的依赖关系,并确保在链接时库的顺序是正确的。
在某些情况下,可能需要在一个 Android.mk 文件中定义的变量在另一个 Android.mk 文件中也可见(例如在包含的子目录中)。这可以通过使用 include (BUILD_XXX) 之前导出变量来实现,但更常见的是使用 include (CLEAR_VARS) 后立即设置 include
如果模块包含多个源文件,可以在 LOCAL_SRC_FILES 中一次性列出所有文件。
LOCAL_SRC_FILES := main.c \
helper.c \
another_helper.c使用 NDK 编译的应用或库可以在 Android 设备或模拟器上调试。可以通过 ndk-build 命令配合 gdbserver 和 gdb 客户端进行调试。
这是一个包含多个元素的 Android.mk 示例:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
LOCAL_SHARED_LIBRARIES := libfoo
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)在这个示例中,我们定义了一个名为 hello-jni 的共享库,它依赖于名为 libfoo 的另一个共享库,并且链接了 Android 的日志库。
LOCAL_PATH 正确设置,以指向包含 Android.mk 文件的目录。include $(CLEAR_VARS) 在定义新模块之前清除变量。Android.mk 的最佳资源。Android.mk 文件可以加深理解。通过以上介绍,你应该对 Android.mk 文件的基本结构和用法有了一定的了解。建议通过实践来加深理解,并查阅官方文档以获取更多高级特性和最佳实践。