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中安装gccg++gdbmakeGUN工具集即可。
(1)Cygwin的安装
Cygwin的下载地址为:
https://cygwin.com/install.html

(2)Cygwin的安装步骤:
下载完setup-x86_64.exe,直接下一步:

这里有三个选项,分别是从网络安装,只下载不安装,从本地目录安装(如果,之前安装过)。可根据自己的实际情况选择。这里选择从网络安装。然后,下一步

这里选择第一项,Direct Connection。然后,下一步

这里下载地址选择mirrors.kernel.org即可。也可选择国内163的镜像地址。

这里选择要安装的包(autoconfautomakemakegccg++gdbpcregawk等)。这里偷懒就直接把AdminDebugDevelDocEditorsShells,当然还有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/passwdmkgroup -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-buildAndroid.mkApplication.mk,所以,有必要了解一下,这三个文件在整个交叉编译过程中起了什么作用。

3、ndk-build

首先,ndk-build是一个shell脚本,目标是帮助你正确的调用NDK的构建脚本。ndk-build<ndk-root-path>(NDK安装目录根路径下)有个ndk-build的shell脚本文件,或ndk-build.cmd的文件。

ndk-build的官方指南为:

https://developer.android.com/ndk/guides/ndk-build.html

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_MODULELOCAL_PATH等)
  • PRIVATE_NDK_APP开头的变量名称(编译系统内部使用)
  • 小写的名称(例如:my-dir,同样,也是作为内部使用)

如果你需要在Android.mk中定义自己的变量,推荐用MY_作为前缀。

(2)NDK定义的变量
CLEAR_VARS
上面已经介绍过了,这里就不在赘述。记住一点,在定义LOCAL_XXX前,必须引用这个脚本。用法:

include $(CLEAR_VARS)

BUILD_SHARED_LIBRARY
该变量指向了一个脚本,这个脚本会收集你在每个模块定义的LOCAL_XXX变量信息,并且这个变量还确定了怎样使用你的源码去编译一个共享库。注意,使用这个变量需要你至少已经定义了LOCAL_MODULELOCAL_SRC_FILES。该变量会使编译系统生成一个以.so结尾的库。用法:

include $(BUILD_SHARED_LIBRARY)

BUILD_STATIC_LIBRARY
该变量是BUILD_SHARED_LIBRARY的一个变体,是用来生成一个静态库。构建系统并不会把静态库包含进你的工程里面,但是可以利用静态库生成共享库。该变量会使编译系统生成一个以.a结尾的库。

include $(BUILD_STATIC_LIBRARY)

PREBUILT_SHARED_LIBRARY
指向一个脚本,这个脚本被用来指定一个预构建的共享库。与BUILD_SHARED_LIBRARYBUILD_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_CFLAGSLOCAL_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_LIBRARIESLOCAL_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,会生成一个较小的归档文件。并不包含目标文件,而是用目标文件的路径替代。有效值是truefalse和空。

注意:如果该模块不是编译为静态块,或者预编译静态库,该值将被忽略。

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:
该变量为可选变量,值为releasedebug。编译应用程序模块的时,可以用来改变优化级别。默认是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文件中有实践过。所以,如果遇到你觉得有问题的地方,欢迎与我交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容