android ndk开发之 android.mk
Android.mk 是用来描述 Android NDK 编译环境下的 NDK 项目的 GNU makefile 的片段, 嘛,我理解的是 Android.mk 文件就是 makefile 文件, 语法和 makefile 写法一致, 只是 ndk 编译环境内置了许多脚本, 这些脚本根据 makefile 的环境变量来编译ndk 工程, 这样也减少了重复的工作, 我们只用给全局变量赋值号,然后执行对应的脚本就可以了, 这比 makefile 简单了许多。
咱们先看下简单的 hello-jni 项目的 Android.mk 是怎么写的:
1 LOCAL_PATH := $(call my-dir) 2 3 include $(CLEAR_VARS) 4 5 6 7 LOCAL_MODULE := hello-jni 8 9 LOCAL_SRC_FILES := hello-jni.c 10 11 12 13 include $(BUILD_SHARED_LIBRARY)
我们来一行一行的去理解一下, 熟悉 makefile 的同学可以发现,这里的语法和其他 makefile 是一样的,这里每一行代表一个功能。
LOCAL_PATH := $(call my-dir)
这个 LOCAL_PATH 是 Android 编译系统用来定位源文件的 my-dir 是 android 编译系统提供的一个宏, 它的值是当前的目录,也就是 makefile 文件所在的目录。 换句话说,my-dir 在不同目录下的 makefile 文件中值是不一样的。
include $(CLEAR_VARS)
这里就是引用 ndk 编译系统的脚本啦, CLEAR_VARS 这个宏的值是系统的一个脚本文件的路径名, 他的功能是清除除了 LOCAL_PATH 之外,其他的 LOCAL_<name> 等全局变量。这个必须要调用,因为我们的 Android 编译系统是单执行的, 编译脚本是根据全局变量编译项目的, 不同的 android.mk 文件全局变量的值会在android.mk文件里面改变, 如果不做清除操作,那么本模块的编译会受到前面的模块的影响。
LOCAL_MODULE := hello-jni
编译结果生成的动态库或者静态库的名字会根据这个变量的值指定。 简而言之这个变量的值就是这个模块的名字。
LOCAL_SRC_FILES := hello-jni.c
这个是指定编译的源文件, 这里是的文件路径是相对于LOCAL_PATH值指定的目录路径
include $(BUILD_SHARED_LIBRARY)
这个是引用编译动态库的脚本, 功能是根据最近调用的include $(CLEAR_VARS)后的变量值, 编译生成动态库。
我们来讨论复杂一点的情况,我们想要编译多个动态库:
LOCAL_PATH := $(call my-dir) #module 1 include $(CLEAR_VARS) LOCAL_MODULE := module1 LOCAL_SRC_FILES := module1.c include $(BUILD_SHRED_LIBRARY) #module 2 include $(CLEAR_VARS) LOCAL_MODULE := module2 LOCAL_SRC_FILES := module2.c include $(BUILD_SHARED_LIBRARY)
当 Android ndk 编译系统编译这个Android.mk 文件的时候, 会产生 libmodule1.so 和 libmodule2.so 动态库。
编译静态库:
静态库是不会被打包到 apk 里面去的,只有动态库才会被打包到 apk 里面去, 那为毛还要编译静态库呢?静态库主要是为了生成动态库, 比如我们的动态库也是可以分成几个模块的,那么我们可以把这几个模块分别编译成静态库,然后在一起编译成动态库, 比如我们代码里面引用了三方的代码, 我们可以将其编译成静态库引入进来。
例:
LOCAL_PATH := $(call my-dir) # #3 party avi library # include $(CLEAR_VARS) LOCAL_MODULE := avilib LOCAL_SRC_FILES := avilib.c platform_posix.c include $(BUILD_STATIC_LIBRARY) # #native module # include $(CLEAR_VARS) LOCAL_MODULE := module LOCAL_SRC_FILES := module.c LOCAL_STATIC_LIBRARYS := avilib include $(BUILD_SHARD_LIBRARY)
根据 LOCAL_STATIC)LIBRARYS 变量的值, 上面编译的静态库 将会被包含进生成的动态库里面。
动态库依赖:
考虑这种情况, 有个模块的功能在好几个动态库里面都用到了, 如果我们都是将这个独立的模块编译成静态库,那么我们所有依赖这个模块的动态库都会将这个静态库的代码包含进去,这样会导致我们的库会变大很多。如果是这种情况的话,我们知道动态库在系统里只会有一份, 所有我们能不能把这个独立的模块编译成动态库,然后让其他模块依赖这个动态库呢? 当然可以(><)
# 3 party avi library .... include $(BUILD_SHARED_LIBRARY) #native module 1 include $(CLEAR_VARS) LOCAL_MODULE := module1 LOCAL_SRC_FILES := module1.c LOCAL_SHARED_LIBRARIES := avilib include $(BUILD_SHARED_LIBRARY) #native module 2 include $(CLEAR_VARS) LOCAL_MODULE := module2 LOCAL_SRC_FILES := module2.c LOCAL_SHARED_LIBRARIES := avilib include $(BUILD_SHARED_LIBRARY)
多个ndk工程共享模块:
虽然我们可以通过静态或者动态库的方式共享模块, 但是我们我们是不是可以依赖某个 ndk 工程呢, 从 ndk r5 开始, android ndk 可以允许通过 ndk 工程依赖模块, 比如上面的例子中的 avilib 模块, 我们也可以通过多个 ndk 工程来依赖:
首先, 将 avilib 源代码移动到 ndk 工程的外面, 比如放在 c:\android\shared-modules\avilib, 当然为了避免冲突,我们可以放到模块名对应的目录下,比如:c:\android\shared-modules\transcode\avilib, 不过我们要注意一点, andorid ndk 编译系统是不接受模块路径里面有空格的。
例:动态库 avalib 模块的 android.mk文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := avalib LOCAL_SRC_FILES := avalib.c platform_posix.c include $(BUILD_SHARED_LIBRARY) #不过我们要清楚一点,这个模块的代码已经不在ndk项目里面了, 如果我们要依赖这个动态库,我们可以通过在android.mk文件的结尾处调用 improt-module 宏函数来导入; #native module incude $(CLEAR_VARS) LOCAL_MODULE := module LOCAL_SRC_FILES := module.c LOCAL_SHARED_LIBRARY := avalib include $(BUILD_SHARED_LIBRARY) $(call import-module,transcode/avilib)
import-module 函数首先需要定位到被倒进ndk工程的共享模块, 默认是在<Android NDK>/ 目录下找, 现在我们想在包涵 c:\android\shared-modules 目录到搜索路径, 我们需要定义一个环境变量 NDK_MODULE_PATH, 然后将共享目录的路径添加到这个环境变量里面去。
直接库依赖,没有源代码
比如我们想提供我们的模块给别人依赖,但是不想提供源代码, 或者依赖一个已经编译好的库, 我们该怎么操作呢?
这种情况我们可以使用 prebuild。不过我们需要一个 android.mk 文件。
例:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := avilib LOCAL_SRC_FILES := avilib.so include $(PREBUILT_SHARED_LIBRARY)
LOCAL_SRC_FILES 没有指向源文件, 这里的 Prebuilt library 没有提供任何关于机器架构的信息,开发者需要确保 prebuilt library 被编译子和 ndk 项目相同的机器架构上。
Prebuilt_shared_LIBRARY 变量的值是 Prebuilt-shared-library.mk 稳健的路径, 它的功能只是将 prebuilt library 复制到 NDK 工程的 libs 目录下, 我们也可以使用 PREBUILT_STATIC_LIBRARY 变量, 和动态库一样的,这个是依赖静态库。 在 ndk 项目里面我们还是和原来的用法一样来依赖这个模块。
例:
... LOCAL_SHARED_LIBRARY := avalib ...
其他的编译系统定义的变量:
TARGET_ARCH :目录cpu架构名字,比如arm
TARGET_PLATFORM :android平台名字,比如android-3
TARGET_ARCH_ABI :目标cpu架构的应用二进制接口(ABI)的名字,比如:armeabi-v7a
TARGET_ABI :连接目标cpu架构和ABI, 比如android-3-armeabi-v7a
可以用来描述模块的变量:
LOCAL_MODULE_FILENAME: 重新定义输出文件的名字, 默认编译系统使用LOCAL_MODULE作为默认输出文件名, 但是我们可以通过这个变量去覆盖。
LOCAL_CPP_EXTENSION: 默认c++源文件的扩展名为.cpp. 这个变量的作用是定义其他的c++源文件扩展名
例如:LOCAL_CPP_EXTENSION := .cpp .cxx
LOCAL_CPP_FEATURES: 表示配置模块的c++ 的特征, 比如RTTI, exceptions等, 我们可以通过配置这个来支持c++的一些特性
例如: LOCAL_CPP_FEATURE := rtti
LOCAL_C_INCLUDES:自定义搜索头文件路径的列表, 可以是相对路径,路径是相对于ndk安装目录,或着系统路径。
例如:
... LOCAL_C_INCLUDES := sources/shared-module LOCAL_C_INCLUDES := $(LOCAL_PATH)/include ...
LOCAL_CFLAGS:当编译c和c++源文件时候,设置 compiler flags
例如:
LOCAL_CFLAGS := -DNODEBUG -DPORT=1234 #-D相当于代码中的宏定义 -I<路径> 头文件路径
LOCAL_CPP_FLAGS:这个是配置编译c++源文件的时候用到的,同上。
LOCAL_WHOLE_STATIC_LIBRARIES: 这个是指定包涵整个静态库到动态库里面去。这个貌似在多个静态模块之间循环依赖的时候会很有用。
LOCAL_LDLIBS: 这个变量使用主要是用来链接系统的动态库。
例:LOCAL_LDLIBS := -llog
LOCAL_ALLOW_UNDEFINED_SYMBOLS:这个变量是关闭检查在产生的文件中缺失符号(miss symbols)
LOCAL_ARM_MODE: 自定义arm 机器架构的产生二进制的类型, 默认编译系统是用16位指令, 但是设置这个变量后可以设置arm 使用32位指令
例:LOCAL_ARM_MODE := arm
这个变量改变了编译系统的在整个模块的行为, 可以用.arm扩展名, 使其只在具体的文件中使用arm模式。
LOCAL_SRC_FILES := file1.c file2.c.arm
LOCAL_ARM_NEON:可选的和ARM机体系结构相关的变量,指示ARM高级单指令多数据(SIMD)(也称为NEON)内部函数应在源文件中启用。
例:LOCAL_ARM_NEON := true
这个变量改变编译系统在整个模块的行为,.neon后缀也可以使用在具体的NEON内
LOCAL_SRC_FILES := file1.c file2.c.neon
LOCAL_DISABLE_NO_EXECUTE:可选变量禁用NX bit安全功能。 NX位,它表示不会执行,是CPU的用于分离的内存区域由代码或存储使用的技术。这可以防止恶意软件通过将其代码到应用程序的存储器区域采取控制应用程序。
例:LOCAL_DISABLE_NO_EXECUTE := true
LOCAL_EXPORT_CFLAGS:该变量允许记录一组将被添加到通过任LOCAL_STATIC_LIBRARIES或LOCAL_SHARED_LIBRARIES使用此模块的任何其他模块的LOCAL_CFLAGS定义编译的标志中, 换句话说如果模块a定义了这个变量,模块b依赖这个变量的话,那么模块b的变量LOCAL_CFLAGS中将会包含模块a的LOCAL_EXPORT_CFLAGS值。
LOCAL_EXPORT_CPPFLAGS:同上, 只是针对具体的c++源文件
LOCAL_EXPORT_LDFLAGS:理解同上
LOCAL_EXPORT_C_INCLUDES:理解同上
LOCAL_SHORT_COMMANDS:这个变量在存在很多的源文件或者静态依赖或者动态依赖的模块中可以设置为true, 因为操作系统对命令行比如window 仅仅允许最多8191个字符, 这个变量会使编译器拆开命令,这样使命令行比最大值更短。当然在小的模块中不建议开启它, 因为他会使编译变慢。
LOCAL_FILTER_ASM:这个变量是过滤掉LOCAL_SRC_FILES记录的集合中的应用。
其他的宏函数:
all-subdir-makefiles:
返回Android.mk列表构建位于当前目录下的所有子目录中的文件。
this-makefile:
返回当前编译的android.mk编译文件的路径。
parent-makefile:
返回包含当前android.mk脚本文件的父android.mk文件的路径
grand-parent-makefile:
和parent-makefile类似,但是指父android.mk的父android.mk文件的路径。