美文网首页AlphaGL的游戏开发之旅【Android】Android
Android游戏开发实践(1)之NDK与JNI开发02

Android游戏开发实践(1)之NDK与JNI开发02

作者: AlphaGL | 来源:发表于2016-12-04 22:10 被阅读1227次

    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文件中有实践过。所以,如果遇到你觉得有问题的地方,欢迎与我交流。

    相关文章

      网友评论

        本文标题:Android游戏开发实践(1)之NDK与JNI开发02

        本文链接:https://www.haomeiwen.com/subject/hgtkmttx.html