Android游戏开发实践(1)之NDK与JNI开发02
Android游戏开发实践(1)之NDK与JNI开发02
承接上篇Android游戏开发实践(1)之NDK与JNI开发01分享完JNI的基础和简要开发流程之后,再来分享下在Android环境下的JNI的开发,以及涉及到的NDK相关的操作。当然,本篇仍是以Eclipse作为开发IDE,虽然Google官方已经不再支持Eclipse了,推荐是用AndroidStudio进行开发。但对于游戏开发来说,IDE的影响并没有那么大,且从Eclipse那个时代过来的,对Eclipse还是感情很深的。后续,还有专门一篇来分享下AndroidStudio的使用以及使用CMake编译等,会提到JNI这方面的内容。
按照惯例,每一篇文章都喜欢附上官方的文档。因为,只有官方的文档才是最准确,最实时,且内容最丰富的。那么,NDK官方开发地址为:
Getting Started with the NDK:
https://developer.android.com/ndk/guides/index.html
本文目录如下:
1、NDK环境搭建
(1)安装CDT
CDT全成是C/C++ DevelopmentTools,安装该插件使得Eclipse也得支持C/C++的开发。须下载和Eclipse版本对应的CDT插件。是喜欢Eclipse的便捷,同时又开发C/C++的必装插件。CDT的下载地址为:
http://www.eclipse.org/cdt/downloads.php
安装成功后,在Eclipse的Window-Preferences中看到多了一项C/C++的支持:
(2)NDK的下载
目前,NDK的最新版本为android-ndk-r13b
,下载地址为:
https://developer.android.com/ndk/downloads/index.html
这里需要说明下,为了方便演示笔者所使用的NDK版本为android-ndk-r8b
。最新版本已经不再支持GCC编译,默认改用Clang。还修复了相关的bug,建议线上的产品更新最新的稳定版。
(3)NDK的集成
将下载好的NDK解压,并将该路径添加到Path环境变量中,然后集成至Eclipse中。如图:
2、交叉编译
NDK编译的环境有很多,但基本都是通过ndk-build
工具来完成的。有直接通过Eclipse安装CDT即可完编译,也可以通过安装Cygwin来编译。事实上,在android-ndk-r7
之后的版本,已经不需要安装Cygwin就可以编译出.so
了。但这里还是想介绍下,因为笔者开发曾用Cygwin编译过一段时间,而且多了解一种编译途径也没什么坏处。当然,熟悉Linux平台或者Mac平台开发的朋友会感觉更亲切些。
2.1 Cygwin编译
Cygwin是一套在Window上模拟类Unix系统环境的工具。而Android底层又是基于Linux的。因此,对Linux环境下的开发支持也更好。只要在Cygwin中安装gcc
、g++
、gdb
、make
等GUN
工具集即可。
(1)Cygwin的安装
Cygwin的下载地址为:
https://cygwin.com/install.html
(2)Cygwin的安装步骤:
下载完setup-x86_64.exe
,直接下一步:
这里有三个选项,分别是从网络安装,只下载不安装,从本地目录安装(如果,之前安装过)。可根据自己的实际情况选择。这里选择从网络安装。然后,下一步
这里选择第一项,Direct Connection。然后,下一步
这里下载地址选择mirrors.kernel.org
即可。也可选择国内163的镜像地址。
这里选择要安装的包(autoconf
、automake
、make
、gcc
、g++
、gdb
、pcre
、gawk
等)。这里偷懒就直接把Admin
、Debug
、Devel
、Doc
、Editors
、Shells
,当然还有Python
。然后,下一步
接着经过漫长的等待,大概下载3,4G的文件。
安装完,运行Cygwin,输入如下命令
(3)在Cygwin下配置NDK环境变量
在当前当前用户目录下运行,(例如,我的目录为D:\env\cygwin\home\John):
配置NDK环境变量,注意别覆盖原来的PATH环境变量。
(4)验证NDK配置
尝试在Cygwin下用ndk-build
来编译NDK下的hello-jni的samples。如图:
可以看到正确编译出
libhello-jni.so
库(在项目目录下的libs
,不同cup架构命名的文件夹里)。如遇到各种权限错误,请将samples下的hello-jni项目的权限修改为可写入、可修改等。
2.2 Eclipse编译
(1)将hello-jni
的项目导入到Eclipse中。
(2)给hello-jni
添加builder,来编译C/C++代码。
右键HelloJni项目,选择Properties
,然后,选择Builders
,点击New
新建一个Builder
。选择Program
,点击OK
即可。
分别填写Builder名称。找到Cygwin的Shell程序和工作目录。将要编译的项目目录和执行的命令当成参数参数Shell命令执行。
注意:
cd
与/cygdrive/d/android-ndk-r8b/samples/hello-jni
中间有个空格。
ANDROID_NDK_ROOT:是在Cygwin中配置NDK环境变量的名称。
通过Cygwin中输入bash --login -h
可以获取更详细的信息:
Your group is currently "mkpasswd". This indicates that your
gid is not in /etc/group and your uid is not in /etc/passwd.
The /etc/passwd (and possibly /etc/group) files should be rebuilt.
See the man pages for mkpasswd and mkgroup then, for example, run
mkpasswd -l [-d] >> /etc/passwd
mkgroup -l [-d] >> /etc/group
如果遇到这种,按照提示在Cygwin终端执行,mkpasswd -l [-d] >> /etc/passwd
与mkgroup -l [-d] >> /etc/group
命令即可。
(3)将配置JNI_Builder
优先级设为最高
(4)编译HelloJni
工程。
选中HelloJni
工程,在Eclipse中选择Project-clean
。这样,Eclipse便可自动编译HelloJni
工程了。
2.3 AndroidStudio和CMake编译
这里就不花篇幅介绍这相关的内容,下一篇专门介绍下AndroidStudio的使用及在AndroidStudio下NDK的开发。希望能给从其它IDE迁移到IntelliJ IDEA系开发或许刚接触AndroidStudio一些启发。所以,这里先留个伏笔。
ndk-build、Android.mk与Application.mk
虽然,已经成功的将samples
下的hell-jni
项目成功编译出了.so
动态库。在整个交叉编译过程中,涉及到了三个比较重要的文件,分别是ndk-build
、Android.mk
和Application.mk
,所以,有必要了解一下,这三个文件在整个交叉编译过程中起了什么作用。
3、ndk-build
首先,ndk-build
是一个shell
脚本,目标是帮助你正确的调用NDK的构建脚本。ndk-build
在<ndk-root-path>
(NDK安装目录根路径下)有个ndk-build
的shell脚本文件,或ndk-build.cmd
的文件。
ndk-build
的官方指南为:
3.1 ndk-build用法
cd $PROJECT_PATH
$ <ndk>/ndk-build
用法:在项目的根目录下,执行ndk-build
脚本命令。
例如:
进到
hello-jni
的项目根目录执行ndk-build
,ANDROID_NDK_ROOT
是在Cygwin中配置NDK环境变量的名称。
$ <ndk>/ndk-build -C <PROJECT_PATH>
用法:在任意目录下执行ndk-build
,用-C
来指定要编译的项目的目录。
例如:
实际上:执行ndk-build
相当于执行了以下命令:
$GNUMAKE -f <ndk>/build/core/build-local.mk
<parameters>
例如:
3.2 ndk-build可选参数
$ ndk-build clean
清除编译生成的二进制文件。
$ ndk-build -C <project>
指定项目路径
$ ndk-build NDK_DEBUG=0
NDK_DEBUG为0是编译为release版,为debug版。
更多的ndk-build
的参数介绍,请参考上面贴出的ndk-build
的官方指南。
4、Android.mk
Android.mk
是用来向编译系统指定项目中C/C++源代码文件编译、链接规则的一种描述文件。它是GUN makefile
文件的一小部分。那么,简单来说,就是用来起指定编译引用的头文件目录、编译出的so的名字、需要编译的源文件或库等作用。熟悉makefile
语法的,肯定熟悉这种用法。Android.mk
文件在$project-path/jni/Android.mk
路径下。
Android.mk
官方说明文档地址为:
https://developer.android.com/ndk/guides/android_mk.html
4.1 Android.mk初级
首先,仍以hello-jni
为例,看看都定义了哪些内容。打开<ndk-path>/samples/hello-jni/jni/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 := $(call my-dir)
:
Android.mk
必须首先定义LOCAL_PATH
,它用来在开发的树文件夹中定位文件的。my-dir
是由编译系统提供的宏,用来返回当前目录的路径。(注意:这个路径是包含了Android.mk
的路径)
include $(CLEAR_VARS)
:
CLEAR_VARS
变量也是由编译系统提供的,include $(CLEAR_VARS)
是引用一个特殊的GUN makefile
文件,这个makefile
文件所做的就是清除定义了很多LOCAL_XXX
(例如: LOCAL_MODULE
,LOCAL_SRC_FILES
,LOCAL_STATIC_LIBRARIES
等)这种格式的变量,但这里不会清除LOCAL_PATH
变量。这么做是很有必要的,因为编译系统解析这些编译控制文件都是在单一的GUN make
上下文环境中,解析出来的LOCAL_XXX
变量都是全局的。
LOCAL_MODULE := hello-jni
:
LOCAL_MODULE
必须是唯一的,且不能包含空格。编译系统会根据这个名字生成相应的共享库,并自动添加前缀和后缀。本例中,最终生成的共享库的名称为libhello-jni.so
。
注意:编译生成的共享库都是
lib
开头的,如果,你声明的名称已经包含lib
(libhello-jni
),那么,最终生成的共享库名称就不添加lib
前缀了,生成的仍为libhello-jni.so
。
LOCAL_SRC_FILES := hello-jni.c
:
LOCAL_SRC_FILES
用来指明要编进共享库(.so
)的C/C++源文件的列表。
注意:这里只需要指定要编译的
.c
或者.cpp
等源文件即可,不需要指定.h
头文件。
include $(BUILD_SHARED_LIBRARY)
:
BUILD_SHARED_LIBRARY
变量是由系统提供,include $(BUILD_SHARED_LIBRARY)
会引用一个Gun makefile
脚本,用来收集你定义的LOCAL_XXX
格式的变量。同时,决定哪些要编译,如何编译等。
注意:显然
BUILD_SHARED_LIBRARY
是用来编译出共享库.so
文件的,同理,也可以使用BUILD_STATIC_LIBRARY
来编译出静态库.a
文件。
以上便是编写一个简单的Android.mk
文件的所有元素。通过上面的描述发现,如果我们要编写一个自己的Android.mk
文件,没有特殊需求的话,可以直接将hello-jni
工程中的Android.mk
文件拷贝,然后,修改库的名称(LOCAL_MODULE
)和要编译的源文件列表(LOCAL_SRC_FILES
)变量即可。
4.2 Android.mk高级
这里来详细了解下Android.mk
其他的一些变量及语法规则。
(1)NDK变量与宏
在Android.mk
中还有一些其他变量,是作为NDK编译系统的保留变量,你只能依赖它或者定义它。这些变量的规则如下:
- 以
LOCAL_
开头的变量名称(如:LOCAL_MODULE
,LOCAL_PATH
等) - 以
PRIVATE_
,NDK_
,APP
开头的变量名称(编译系统内部使用) - 小写的名称(例如:
my-dir
,同样,也是作为内部使用)
如果你需要在Android.mk中定义自己的变量,推荐用MY_
作为前缀。
(2)NDK定义的变量
CLEAR_VARS:
上面已经介绍过了,这里就不在赘述。记住一点,在定义LOCAL_XXX
前,必须引用这个脚本。用法:
include $(CLEAR_VARS)
BUILD_SHARED_LIBRARY:
该变量指向了一个脚本,这个脚本会收集你在每个模块定义的LOCAL_XXX
变量信息,并且这个变量还确定了怎样使用你的源码去编译一个共享库。注意,使用这个变量需要你至少已经定义了LOCAL_MODULE和LOCAL_SRC_FILES。该变量会使编译系统生成一个以.so
结尾的库。用法:
include $(BUILD_SHARED_LIBRARY)
BUILD_STATIC_LIBRARY:
该变量是BUILD_SHARED_LIBRARY的一个变体,是用来生成一个静态库。构建系统并不会把静态库包含进你的工程里面,但是可以利用静态库生成共享库。该变量会使编译系统生成一个以.a
结尾的库。
include $(BUILD_STATIC_LIBRARY)
PREBUILT_SHARED_LIBRARY:
指向一个脚本,这个脚本被用来指定一个预构建的共享库。与BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY不同,LOCAL_SRC_FILES的值不能是一个源文件,它必须是一个单独的指向预构建的共享库的路径,例如:foo/libfoo.so。用法:
PREBUILT_STATIC_LIBRARY:
该变量与PREBUILT_SHARED_LIBRARY相同,只是指向的一个预构建的静态库。
TARGET_ARCH:
这个变量是目标CPU架构的名字,就像Android Open Source Project里面指定了目标CPU架构。这个变量用于任意的ARM兼容的构建,或者ARM,或者是独立于CPU结构的修订,或者ABI。
TARGET_PLATFORM:
编译到目标平台的api等级。例如,Android5.1对应的是Android api22。用法:
TARGET_PLATFORM := android-22
TARGET_ARCH_ABI:
当编译系统解析Android.mk文件的时候,这个变量存储CPU和架构的名字。你可以指定一个或者多个下面列出的名字,使用空格分隔两个名字
用法:
TARGET_ARCH_ABI := arm64-v8a
注意:android-ndk-1.6_r1之前这个这个变量被定义为arm。
TARGET_ABI:
该变量将API的级别和ABI联系在一起,当你在真机上调试系统的时候特别有用。用法:
TARGET_ABI := android-22-arm64-v8a
LOCAL_MODULE_FILENAME:
这是一个可选变量。允许你重新指定一个变量的名称来覆盖默认生成的名称。例如:强制生成libnewfoo.so
LOCAL_MODULE := foo
LOCAL_MODULE_FILENAME := libnewfoo
LOCAL_MODULE_FILENAME不需要指定文件路径或扩展名
LOCAL_SRC_FILES:
该变量用来指定要编译的源文件列表。这里推荐使用相对路径。用法:
LOCAL_SRC_FILES := foo1.c \
../Module2/foo2.c
注意:使用Unix风格的/,多个文件使用\换行,注意\后面没有空格。
LOCAL_CPP_EXTENSION:
同样,LOCAL_CPP_EXTENSION也是一个可选变量。用来指定C++源文件的扩展名。默认是.cpp
。从NDK r7版本后,可以指定一系列的扩展名。用法:
LOCAL_CPP_EXTENSION := .cxx .cpp .cc
LOCAL_CPP_FEATURES:
同样,LOCAL_CPP_FEATURES也是一个可选变量。如果,你用到了C++的一些特殊功能(例如:RTTI,异常支持等),并且正确的编译和链接,可以使用该变量来声明。用法:
LOCAL_CPP_FEATURES := rtti exceptions
建议使用该变量来代替LOCAL_CPPFLAGS中直接定义
-frtti
、-fexceptions
这种用法
LOCAL_C_INCLUDES:
可选变量,可以通过该变量来指定一个相对于NDK根目录的路径列表,并在编译C/C++时添加到搜索路径中。用法:
LOCAL_C_INCLUDES := $(LOCAL_PATH)//foo
注意:该声明要放在LOCAL_CFLAGS和LOCAL_CPPFLAGS的声明前面。
LOCAL_CFLAGS:
可选变量,在编译C/C++源代码时,能给编译器传递一个编译标志集合。用来指定附加宏的定义和编译选项是很有用的。
LOCAL_CPPFLAGS:
可选变量,在编译C++源代码时(只编译C++),能给编译器传递一个编译标志集合。
LOCAL_STATIC_LIBRARIES:
该变量定义了本模块编译链接过程中用到的静态库列表。
LOCAL_SHARED_LIBRARIES:
该变量定义了本模块编译链接过程中用到的共享库列表。
LOCAL_WHOLE_STATIC_LIBRARIES:
该变量跟LOCAL_STATIC_LIBRARIES类似,不同的是在编译链接过程中会载入静态库的所有源代码目标文件。这在解决几个库之间循环引用时,非常有用。可以通过GUN的--whole-archive
标志来查看相关说明。
LOCAL_LDLIBS:
该变量用来定义本模块编译时用到的附加的链接器选项。用-l
前缀指定。例如:要链接/system/lib/libz.so
LOCAL_LDLIBS := -lz
注意:如果,你编译一个静态库是定义了该变量,编译系统会忽略它,并且
ndk-build
会打印一个警告。
LOCAL_LDFLAGS:
该变量定义了在编译给编译系统传递一些其他的链接标志。用法:
LOCAL_LDFLAGS += -fuse-ld=bfd
注意:如果,你编译一个静态库是定义了该变量,编译系统会忽略它,并且
ndk-build
会打印一个警告。
LOCAL_ALLOW_UNDEFINED_SYMBOLS:
默认情况下,在编译一个共享库的时候,任何未定义的引用,将会抛出"符号未定义(undefined symbol)"的错误。该变量能帮助你捕捉代码中的bug。如果,要禁用这项检查可以把该变量设置为true
。这么设置
注意:如果,你编译一个静态库是定义了该变量,编译系统会忽略它,并且
ndk-build
会打印一个警告。
LOCAL_ARM_MODE:
默认情况下,编译系统会在ARM平台"thumb"模式下生成16位的二进制文件。定义该变量则强制生成32位"arm"模式下的对象文件。例如:
LOCAL_ARM_MODE := arm
你也可以加上.arm
后缀来告诉编译系统,只想对某个源文件使用arm指令。例如:
LOCAL_SRC_FILES := foo.c bar.c.arm
LOCAL_ARM_NEON:
只有当目标平台为armeabi-v7a
指令集时,才定义它。它允许你的C/C++代码中使用ARM高级指令。也可以在汇编文件中使用NEON
指令。你可以使用.neon
后缀来指定编译器支持NEON指令编译。例如:
LOCAL_SRC_FILES = foo.c.neon bar.c zoo.c.arm.neon
注意:不是所有的ARMv7架构的CPU都支持NEON扩展。
LOCAL_DISABLE_NO_EXECUTE:
Android NDK r4版本开始支持这种"NX bit"的安全功能。默认是启用的,你也可以设置该变量的值为true来禁用它。但不推荐这么做。该功能不会修改ABI,只在ARMv6+CPU的设备内核上启用。
LOCAL_DISABLE_RELRO:
默认情况下,NDK编译代码是只读重定位和GOT保护的。这个会指示运行时链接器标记特定的内存区是只读的,在移动位置之后。这样会使得某些安全漏洞(如GOT覆盖)更难执行。默认是启用的,你也可以把该变量的值设为true来禁用。但不推荐这么做。
LOCAL_DISABLE_FORMAT_STRING_CHECKS:
默认情况下,编译系统编译代码时会检查格式化字符串,如果printf样式的函数使用了非常严格的字符串,那么编译出错。默认是开启的,你也可以通过设置该变量的值为true来禁用。但不推荐这么做。
LOCAL_EXPORT_CFLAGS:
该变量是用来记录C/C++编译器标志集合,这些编译器标志会被添加到通过变量LOCAL_STATIC_LIBRARIES或LOCAL_SHARED_LIBRARIES使用本模块的其他模块的LOCAL_CFLAGS变量中。例如:
include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_EXPORT_CFLAGS := -DFOO=1
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.c
LOCAL_CFLAGS := -DBAR=2
LOCAL_STATIC_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)
编译bar
模块时,"-DFOO=1 -DBAR=2"标志将一起传递给编译器。
LOCAL_EXPORT_CPPFLAGS:
该变量与LOCAL_EXPORT_CFLAGS类似,但只适用于C++。
LOCAL_EXPORT_C_INCLUDES:
该变量与LOCAL_EXPORT_CFLAGS类似,但是该变量用来包含路径的。例如,上例中bar.c
需要包含foo
模块的头文件。
LOCAL_EXPORT_LDFLAGS:
该变量与LOCAL_EXPORT_CFLAGS类似,但是它用作链接器标志。
LOCAL_EXPORT_LDLIBS:
该变量与LOCAL_EXPORT_CFLAGS一样,该变量的值将会被添加到其它模块引用到本模块的其它模块的LOCAL_LDLIBS变量中。例如:
include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_EXPORT_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.c
LOCAL_STATIC_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)
编译bar
的时候,会在链接log的系统日志库。
LOCAL_SHORT_COMMANDS:
当你的模块有很多源代码文件或依赖很多静态库或者共享库时,设置为true。这样会强制编译系统使用@语法来包含中间对象或链接库来生成归档文件。
注意:这个功能在Windows上很有用,因为Windows上的命令行支持的最大字符数为8191个,这对于复杂的项目来说太小。默认是不推荐启用这个功能,因为它会使编译变得很慢。
LOCAL_THIN_ARCHIVE:
编译静态库是,如果该变量值设为true,会生成一个较小的归档文件。并不包含目标文件,而是用目标文件的路径替代。有效值是true
,false
和空。
注意:如果该模块不是编译为静态块,或者预编译静态库,该值将被忽略。
LOCAL_FILTER_ASM:
该变量的值将作为一个Shell命令,它会过滤从LOCAL_SRC_FILES生成的文件或汇编文件。定义该变量将会发生下面的情况:
- 编译系统会将C/C++源文件生成临时的汇编文件,而不是将他们编译到目标文件中。
- 编译系统会对汇编文件和LOCAL_SRC_FILES中列出的文件执行LOCAL_FILTER_ASM中的Shell命令,生成另外一个汇编文件。
- 编译系统将这些过滤后的汇编文件编译进目标文件。
NDK提供的函数宏
NDK提供了GNU Make的函数宏。使用$(call <function>)
的方式调用,它们会返回相应的文本信息。
my-dir:
该宏返回的是最后包含的makefile文件路径,一般是当前Android.mk的路径。my-dir
对于Android.mk开头定义的LOCAL_PATH变量很有用。例如:
LOCAL_PATH := $(call my-dir)
由于GNU Make的工作方式,这个宏返回的是构建系统在解析构建脚本时包含的最后一个makefile的路径。因此,你不应该在include其他的文件之后再继续使用my-dir
。例如:
LOCAL_PATH := $(call my-dir)
# ... declare one module
include $(LOCAL_PATH)/foo/`Android.mk`
LOCAL_PATH := $(call my-dir)
# ... declare another module
这里的问题在于第二个my-dir
的调用将LOCAL_PATH的值设置为了$PATH/foo
,因为$PATH/foo
才是最近包含的路径。你可以通过在Android.mk文件中放置额外的包含来避免这个问题。例如:
LOCAL_PATH := $(call my-dir)
# ... declare one module
LOCAL_PATH := $(call my-dir)
# ... declare another module
# extra includes at the end of the Android.mk file
include $(LOCAL_PATH)/foo/Android.mk
如果这种方式不可行,那么可以将第一次调用my-dir
的值存在另外一个变量里面,例如:
MY_LOCAL_PATH := $(call my-dir)
LOCAL_PATH := $(MY_LOCAL_PATH)
# ... declare one module
include $(LOCAL_PATH)/foo/`Android.mk`
LOCAL_PATH := $(MY_LOCAL_PATH)
# ... declare another module
all-subdir-makefiles:
该宏返回的是当前my-dir路径下的所有子目录中的Android.mk文件的列表。你可以使用此函数向构建系统提供深层嵌套的源目录层次结构。默认情况下,NDK仅查找包含Android.mk文件的目录中的文件。
this-makefile:
该宏返回的是当前makefile文件的路径(从构建系统调用这个函数中)。
parent-makefile:
该宏返回当前目录树中的父makefile的路径(包含当前makefile的makefile路径)。
grand-parent-makefile:
该宏返回包含树中的祖父类makefile的路径(包含当前makefile的makefile的路径)。
import-module:
该宏允许你通过模块的名称找到并包含模块的Android.mk文件。例如:
$(call import-module,<name>)
在这个示例中,构建系统会根据NDK_MODULE_PATH这个环境变量所指示的目录里面寻找名为<name>
的模块,然后自动为你include对应的Android.mk文件。
5、Application.mk
通过上面的介绍,大体了解了Android.mk文件的用法及规则。但通常编译本地C/C++代码光有Android.mk还不够,还得需要Application.mk文件。Application.mk也是一种makefile文件,跟Android.mk有相似之处。如果说,Android.mk用来描述单独某个模块的编译规则的描述文件,那么Application.mk则是描述整个应用程序的模块的描述文件。Application.mk文件一般在$project-path/jni/Application.mk
下($project-path是项目根目录)。当然,也可以放在$NDK/apps/<myapp>/Application.mk
路径下。这两种方式,造成Application.mk
也有细微的区别。
(1)变量
APP_PROJECT_PATH:
该变量用来指定项目根目录的绝对路径。
注意:假如
Application.mk
文件的路径是$NDK/apps/<myapp>/Application.mk
,那么该变量为强制定义的。如果,Application.mk
文件在$project-path/jni/Application.mk
路径下,则是可选变量。
APP_OPTIM:
该变量为可选变量,值为release
或debug
。编译应用程序模块的时,可以用来改变优化级别。默认是release
模式,并且会生成高度优化的二进制文件。debug
模式生成的是未优化的二进制代码,但更容易调试。
APP_CFLAGS:
在编译任何模块的任何C/C++代码时,构建系统会通过该变量给编译器传递一个C编译标志集合。你可以使用该变量根据应用程序中给定的模块的需要来改变其构建,而不需要修改Android.mk文件本身了。
这些标志的所有路径必须相对于NDK的顶级目录。例如:如果你有以下设置:
sources/foo/Android.mk
sources/bar/Android.mk
在编译期间,你需要在foo/Android.mk中指定你要添加的foo的源路径,你应该使用:
APP_CFLAGS += -Isources/bar
或者:
APP_CFLAGS += -I$(LOCAL_PATH)/../bar
使用-I../bar
将不能正常工作,因为它等价于-I$NDK_ROOT/../bar
。
注意:在android-ndk-1.5_r1版本中,该变量只适用于C,不能作用于C++。之后的所有的版本,该变量可适用C/C++源码上。
APP_CPPFLAGS:
该变量包含一组C++编译器标志,构建系统仅在构建C++源代码时传递给编译器。
APP_LDFLAGS:
在链接的时候,构建系统系统会想链接器传递一组链接标志。该变量仅在构建系统构建共享库和可执行文件的时候才生效,当构建静态库时,将被忽略。
APP_BUILD_SCRIPT:
默认情况下,NDK构建系统会在$project-path/jni/
目录下查找Android.mk文件。如果你想修改此行为,你可以定义APP_BUILD_SCRIPT
变量,并指向备用的构建脚本。编译系统总是将一个非绝对路径解释为NDK的顶级目录。
APP_ABI:
默认情况下,编译系统会根据armeabi ABI
生成机器码。该机器码基于ARMv5TE并且支持浮点运算的CPU。你可以使用APP_ABI参数来指定不同的ABI。不同的指令集的APP_ABI设置如下:
注意:all是android-ndk-r7版本开始支持的。
你也可以指定多个值,将它们放在同一行,中间用空格隔开。例如:
APP_ABI := armeabi armeabi-v7a x86 mips
APP_PLATFORM:
此变量包含目标Android的名称。例如:android-3对应的是Android1.5的系统镜像。
APP_STL:
默认情况下,NDK构建系统只为最小的C++运行库(/system/lib/libstdc++.so)提供C++头文件。此外,你还可以在自己的应用程序中使用或链接其他C++实现。可以使用APP_STL
来选择其中的一个。例如:
APP_STL := stlport_static
APP_STL := stlport_shared
APP_STL := system
APP_SHORT_COMMANDS:
该变量相当于整个项目的Android.mk中定义了LOCAL_SHORT_COMMANDS。
NDK_TOOLCHAIN_VERSION:
将此变量定义为4.9版本的GCC编译器。在android-ndk-r13默认是Clang编译器。
APP_PIE:
从Android 4.1(API级别16)开始,Android的动态链接器支持位置无关可执行文件(PIE)。从Android 5.0(API级别21),可执行文件需要PIE。要使用PIE构建可执行文件,需设置-fPIE
标志。这个标志会使得通过随机代码的位置来查找内存损坏的bug更加困难。默认情况下,如果您的项目目标为Android-16或更高版本,ndk-build会自动将此值设置为true。您可以将其手动设置为true或false。
注意:此标志仅适用于可执行文件。它在构建共享或静态库时没有任何作用。
APP_THIN_ARCHIVE:
相当于在Android.mk文件中为此项目中的所有静态库模块设置LOCAL_THIN_ARCHIVE的默认值。
以上内容可能不一定完全正确,大部分内容是根据Android官方文档,通过自己的理解转述的。并没有每个变量在.mk
文件中有实践过。所以,如果遇到你觉得有问题的地方,欢迎与我交流。