在Android NDK开发中,有两个重要的文件:Android.mk和Application.mk,各尽其责,指导编译器如何编译程序,并决定编译结果是什么。本文将详细说明几个常见的NDK选项的配置,帮助大家理解相应的配置选项。
一、Application.mk
Application.mk实际上是轻量级Makefile,通常在$PROJECT/jni目录下,用于配置所有modules的编译变量,例子如下:
APP_ABI := armeabi arm64-v8a x86_64 x86 armeabi-v7a
NDK_TOOLCHAIN_VERSION := clang3.5
APP_STL := stlport_static
APP_OPTIM:= debuge
1、APP_ABI(目标平台ABI类型)
NDK编译中,APP_ABI默认选择armeabi ABI,可通过设置APP_ABI设置一个或者多个ABI,表一为不同的APP_ABI所对应的指令集。
Instrunction set | Value |
ARMv5TE based CPU | APP_ABI := armeabi |
ARMv7 based CPU | APP_ABI := armeabi-v7a |
ARMv8 AArch64 | APP_ABI := arm64-v8a |
IA-32 | APP_ABI := x86 |
Intel64 | APP_ABI := x86_64 |
MIPS32 | APP_ABI := mips |
MIPS64(r6) | APP_ABI := mips64 |
All supported instruction sets | APP_ABI := all |
表一:ABI类型
在开发时可根据需求选择APP_ABI,对于ABI的选择需要考虑到效率和APK大小。由于armeabi-v7a指令集兼容armeabi;市面上的x86手机为了兼容性,基本都使用libhoudini模块,兼容arm指令集;64位机型默认支持32位abi的so,因此在对大小要求比较高的情况下,可以只选择市面上设备基本兼容的armeabi ABI,如果对性能有些许要求,可以再添加x86 ABI。
2、 NDK_TOOLCHAIN_VERSION(编译器类型、版本)
默认采用的是GCC编译器,对于GCC版本的选择与NDK版本有关系,本人使用的是NDK R12,在64位ABI默认是GCC 4.9,32位ABI默认是GCC 4.8,当然也可以像上面例子中给出的设置一样,设置clang编译器。
3、 APP_STL(运行库类型)
Android NDK 默认使用的是最小支持的C++运行库,如果你需要你的NDK程序中使用STL,则可以设置APP_STL := stlport_static,APP_STL有表二中的几种取值。
Name | Explanation |
system(default) | 系统默认的C++运行库 |
stlport_static | 以静态链接方式使用的sttport版本的STL |
stlport_shared | 以动态链接方式使用的sttport版本的STL |
gnustl_static | 以静态链接方式使用的gnustl版本的STL |
gnustl_shared | 以动态链接方式使用的gnustl版本的STL |
gabi++_static | 以静态链接方式使用的gabi++ |
gabi++_shared | 以动态链接方式使用的gabi++ |
c++_static | 以静态链接方式使用的LLVM libc++ |
c++_shared | 以动态链接方式使用的LLVM libc++ |
表二:NDK运行库
若APK中有多个SO文件用到STL,建议都使用动态方式链接STL,这样可以减小整个APK文件大小。
另外需要注意的是官方提供的NDK运行库除了默认的以外都支持RTTI和异常,然而默认是禁用的,将在下面的Android.mk中说明如何开启。
4、APP_OPTIM(编译模式)
“release”模式为默认的,生成的是优化后的二进制;也可以设置为“debug”模式,“debug”模式生成的是未优化二进制,提供很多BUG信息,便于调试和分析。
还有其他配置选项,有兴趣可以查看Application.mk官方文档。
二、Android.mk
Android.mk也是一个轻量级的Makefile,其将C/C++源码组织到一个个module中,module可以是静态库、共享库或者独立的可执行文件, 一个Android.mk文件可以有一个,也可以是多个module,modules之间也可以有依赖关系。
1、基本概念
Android.mk中包括NDK提供的宏、变量以及模块描述变量,这些宏、变量以及变量的赋值共同组成了Android.mk文件,其在NDK编译中各尽其责,指导着NDK的编译。
宏:包括my-dir、all-subdir-makefiles等,通过‘$(call
变量:包括CLEAR_VARS、BUILD_SHARED_LIBRARY、TARGET_ARCH等,由NDK编译系统提供,并且在Android.mk文件被解析前就已经存在。Android.mk文件有可能被多次解析,因此每次解析时这些变量的值都有可能不同。
模块描述变量:Module-description,包括LOCAL_PATH、LOCAL_MODULE、LOCAL_SRC_FILES等LOCAL_前缀变量,这些变量除LOCAL_PATH外,均填写在语句include $(CLEAR_VARS)和include $(BUILD_XXX)之间。
其他Android.mk配置可以查看Android.mk官方文档。
2、基础
在Android.mk中包括一些很基础的变量,下面的栗子包括了基础的变量,本人将详细说明。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH(当前目录)
LOCAL_PATH为模块描述变量,一个Android.mk必须定义LOCAL_PATH,用于定位源文件,在本例中,使用的是编译系统提供的宏“my-dir”(“my-dir”返回最近一次包括Makefile文件路径,通常为当前Android.mk所在目录),用于返回当前目录。
此变量不会被CLEAR_VARS清除,所以每个Android.mk文件只需要定义一次就可以了。
CLEAR_VARS(变量清除)
CLEAR_VARS变量由编译系统提供,顾名思义,作用是清除模块变量(在include $(CLEAR_VARS)和include $(BUILD_XXX)之间的LOCAL_XXX模块变量),当然LOCAL_PATH除外。由于所有的编译控制文件都是单一的GNU Make可执行上下文环境中解析,而这个上下文环境中所有的变量都是全局的,所以编译module前需要清理相应变量。
LOCAL_MODULE(module名称)
LOCAL_MODULE是Android.mk文件中module的唯一标识,这个名字必须是唯一的,且中间不能有空格。在默认情况下,它决定了生成的文件名,如“hello-jni”对应的动态库名称为libhello-jni.so,然而要索引它时,需要“hello-jni”即可,也可以通过变量LOCAL_MODULE_FILENAME来覆盖这个默认名称。
LOCAL_SRC_FILES (源码文件)
LOCAL_SRC_FILES 变量包括C/C++源文件列表,这些源文件会被编译到一个module中,不过也不必列出头文件和包括文件,编译系统会自动为你找打所有需要的依赖关系。值得注意的是linux下路径使用顺斜杠(/)。
BUILD_SHARED_LIBRARY(动态库编译)
BUILD_SHARED_LIBRARY是编译器提供的变量,表示编译成动态库,它指向一个GNU Makefile脚本,这个脚本收集从include $(CLEAR_VARS)后所有的LOCAL_XXX变量中定义的所有信息,决定编译什么以及怎么编译。
还有BUILD_STATIC_LIBRARY,和BUILD_SHARED_LIBRARY类似,表示编译成静态库,静态库不会被拷贝到APK中。
PREBUILT_SHARED_LIBRARY(预编译)
指向一个编译脚本,用来指定一个预编译动态库.使用此变量时,不像BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY那样,LOCAL_SRC_FILES的值必须是只能有一个指向预编译动态库的路径,如foo/libfoo.so,而不是源文件。如下栗子。
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libtest.so
include $(PREBUILT_SHARED_LIBRARY)
PREBUILD_STATIC_LIBRARY和PREBUILD_SHARED_LIBRARY一样,只不过是用于引用静态库。
TARGET_ARCH_ABI(目标ABI名称)
如表一所示,目标ABI名称。若定义了多个ABI,则每次解析Android.mk时,值都不一样,主要使用场景为根本不同的ABI定义不同的文件等。
3、其他模块变量LOCAL_LDLIBS(链接库)
用于额外链接选项,所有的库都有“-l”前缀。可同时列出多个库,用空格隔开,例如:
LOCAL_LDLIBS := -llog -ldl
Android NDK默认链接了多个库,不需要显示的添加到LOCAL_LDLIBS中,包括 the standard C libraries,the standard C++ libraries,real-time extensions和 pthread库。同时也提供了一些需要显示添加的库,这些库版本有关系,如表三所示。
Android level | Lib | Explanation |
Android-3 |
-llog | Android Log |
-lz | Zlib Compression Library | |
-ldl | Dynamic Linker Library | |
Android-4 | -lGLESv1_CM | OpenGL ES 1.x Library |
Android-5 | -lGLESv2 | OpenGL ES 2.0 Library |
Android-8 | -ljnigraphics | The jnigraphics Library |
Android-9 |
-lEGL | The EGL graphics library |
-lOpenSLES | Open ES native audio Library | |
-landroid | Natice Android API | |
Android-14 | -lOpenMAXAL | OpenMAX AL natice multimedia Library |
Android-18 | -lGLESv3 | OpenGL ES 3.0 Library |
Android-21 | -lGLESv3 | OpenGL ES 3.1 Library |
表三:链接库
LOCAL_CFLAGS、LOCAL_CPPFLAGS和LOCAL_LDFLAGS(编译、链接标志)
LOCAL_CFLAGS定义的是在编译C/C++时,传递给编译器的标志集合,LOCAL_CPPFLAGS只支持C++,作用也是传递给编译器一些信息,LOCAL_LDFLAGS是指传递给连接器一些额外的参数。
在NDK开发中难免会用到这些标志位,特别是在优化编译时,下面的是本人在开发中遇到的编译选项。
① LOCAL_CPPFLAGS += -fexceptions
由于NDK编译从R5开始才支持C++异常控制,为了通用性,异常处理默认是禁用的(-fno-exceptions),因此需要在指定module中添加LOCAL_CPPFLAGS += -fexceptions编译选项方可编译带异常处理的C++代码。也可以直接在Application.mk中配置APP_CPPFLAGS += -fexceptions。
② LOCAL_CPPFLAGS += -frtti
从NDK R5开始,NDK也开始支持C++ RTTI了,但为了通用性,所有的C++源文件被构建的时候默认是不支持RTTI的(-fno-rtti),可以通过在Android.mk中添加:LOCAL_CPPFLAGS += -frtti或者在Application.mk添加APP_CPPFLAGS += -frtti来开启RTTI。
③ LOCAL_CFLAGS += -fvisibility=hidden
在NDK开发中,源文件的函数都有一个默认的visibility属性为public,编译生成的so文件中几乎所有的函数名、全局变量名均被导出,其实只需要导出java_com开头的jni函数即可,其他函数不需要暴露出来,在Android.mk中设置LOCAL_CFLAGS += -fvisibility=hidden,就可以隐藏不需要导出的函数,若某个函数需要导出,则添加JNIEXPORT或者__attribute__ ((visibility ("default")))即可。
除了安全,不导出不必要的函数外,还能减小so体积。
④ LOCAL_CFLAGS += -ffunction-sections
不添加此参数时,编译文件.o中代码部分只有.text段,使用此参数,会使每个函数单独有一个段,举个栗子,函数func1()会编译成.text.func1段,虽然段多了,但对链接后代码大小并没有影响。
⑤ LOCAL_CFLAGS += -fdata-sections
同上,每个data都有一个单独的段。
⑥ LOCAL_LDFLAGS += -Wl --gc-sections
-Wl,