Android游戲開發(fā)實(shí)踐(1)之NDK與JNI開發(fā)02

Android游戲開發(fā)實(shí)踐(1)之NDK與JNI開發(fā)02

承接上篇Android游戲開發(fā)實(shí)踐(1)之NDK與JNI開發(fā)01分享完JNI的基礎(chǔ)和簡要開發(fā)流程之后,再來分享下在Android環(huán)境下的JNI的開發(fā),以及涉及到的NDK相關(guān)的操作。當(dāng)然,本篇仍是以Eclipse作為開發(fā)IDE,雖然Google官方已經(jīng)不再支持Eclipse了,推薦是用AndroidStudio進(jìn)行開發(fā)。但對于游戲開發(fā)來說,IDE的影響并沒有那么大,且從Eclipse那個時代過來的,對Eclipse還是感情很深的。后續(xù),還有專門一篇來分享下AndroidStudio的使用以及使用CMake編譯等,會提到JNI這方面的內(nèi)容。

按照慣例,每一篇文章都喜歡附上官方的文檔。因?yàn)?,只有官方的文檔才是最準(zhǔn)確,最實(shí)時,且內(nèi)容最豐富的。那么,NDK官方開發(fā)地址為:

Getting Started with the NDK:
https://developer.android.com/ndk/guides/index.html

本文目錄如下:

1、NDK環(huán)境搭建

(1)安裝CDT
CDT全成是C/C++ DevelopmentTools,安裝該插件使得Eclipse也得支持C/C++的開發(fā)。須下載和Eclipse版本對應(yīng)的CDT插件。是喜歡Eclipse的便捷,同時又開發(fā)C/C++的必裝插件。CDT的下載地址為:
http://www.eclipse.org/cdt/downloads.php
安裝成功后,在Eclipse的Window-Preferences中看到多了一項(xiàng)C/C++的支持:

(2)NDK的下載
目前,NDK的最新版本為android-ndk-r13b,下載地址為:
https://developer.android.com/ndk/downloads/index.html
這里需要說明下,為了方便演示筆者所使用的NDK版本為android-ndk-r8b。最新版本已經(jīng)不再支持GCC編譯,默認(rèn)改用Clang。還修復(fù)了相關(guān)的bug,建議線上的產(chǎn)品更新最新的穩(wěn)定版。

(3)NDK的集成
將下載好的NDK解壓,并將該路徑添加到Path環(huán)境變量中,然后集成至Eclipse中。如圖:

2、交叉編譯

NDK編譯的環(huán)境有很多,但基本都是通過ndk-build工具來完成的。有直接通過Eclipse安裝CDT即可完編譯,也可以通過安裝Cygwin來編譯。事實(shí)上,在android-ndk-r7之后的版本,已經(jīng)不需要安裝Cygwin就可以編譯出.so了。但這里還是想介紹下,因?yàn)楣P者開發(fā)曾用Cygwin編譯過一段時間,而且多了解一種編譯途徑也沒什么壞處。當(dāng)然,熟悉Linux平臺或者M(jìn)ac平臺開發(fā)的朋友會感覺更親切些。

2.1 Cygwin編譯

Cygwin是一套在Window上模擬類Unix系統(tǒng)環(huán)境的工具。而Android底層又是基于Linux的。因此,對Linux環(huán)境下的開發(fā)支持也更好。只要在Cygwin中安裝gcc、g++、gdbmakeGUN工具集即可。
(1)Cygwin的安裝
Cygwin的下載地址為:
https://cygwin.com/install.html

(2)Cygwin的安裝步驟:
下載完setup-x86_64.exe,直接下一步:

這里有三個選項(xiàng),分別是從網(wǎng)絡(luò)安裝,只下載不安裝,從本地目錄安裝(如果,之前安裝過)??筛鶕?jù)自己的實(shí)際情況選擇。這里選擇從網(wǎng)絡(luò)安裝。然后,下一步

這里選擇第一項(xiàng),Direct Connection。然后,下一步

這里下載地址選擇mirrors.kernel.org即可。也可選擇國內(nèi)163的鏡像地址。

這里選擇要安裝的包(autoconf、automakemake、gcc、g++、gdb、pcre、gawk等)。這里偷懶就直接把Admin、Debug、Devel、Doc、EditorsShells,當(dāng)然還有Python。然后,下一步

接著經(jīng)過漫長的等待,大概下載3,4G的文件。


安裝完,運(yùn)行Cygwin,輸入如下命令

(3)在Cygwin下配置NDK環(huán)境變量
在當(dāng)前當(dāng)前用戶目錄下運(yùn)行,(例如,我的目錄為D:\env\cygwin\home\John):


配置NDK環(huán)境變量,注意別覆蓋原來的PATH環(huán)境變量。

(4)驗(yàn)證NDK配置
嘗試在Cygwin下用ndk-build來編譯NDK下的hello-jni的samples。如圖:


可以看到正確編譯出libhello-jni.so庫(在項(xiàng)目目錄下的libs,不同cup架構(gòu)命名的文件夾里)。如遇到各種權(quán)限錯誤,請將samples下的hello-jni項(xiàng)目的權(quán)限修改為可寫入、可修改等。

2.2 Eclipse編譯

(1)將hello-jni的項(xiàng)目導(dǎo)入到Eclipse中。



(2)給hello-jni添加builder,來編譯C/C++代碼。
右鍵HelloJni項(xiàng)目,選擇Properties,然后,選擇Builders,點(diǎn)擊New新建一個Builder。選擇Program,點(diǎn)擊OK即可。


分別填寫B(tài)uilder名稱。找到Cygwin的Shell程序和工作目錄。將要編譯的項(xiàng)目目錄和執(zhí)行的命令當(dāng)成參數(shù)參數(shù)Shell命令執(zhí)行。

注意: cd/cygdrive/d/android-ndk-r8b/samples/hello-jni中間有個空格。
ANDROID_NDK_ROOT:是在Cygwin中配置NDK環(huán)境變量的名稱。

通過Cygwin中輸入bash --login -h可以獲取更詳細(xì)的信息:

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終端執(zhí)行,mkpasswd -l [-d] >> /etc/passwdmkgroup -l [-d] >> /etc/group命令即可。

(3)將配置JNI_Builder優(yōu)先級設(shè)為最高

(4)編譯HelloJni工程。
選中HelloJni工程,在Eclipse中選擇Project-clean。這樣,Eclipse便可自動編譯HelloJni工程了。

2.3 AndroidStudio和CMake編譯

這里就不花篇幅介紹這相關(guān)的內(nèi)容,下一篇專門介紹下AndroidStudio的使用及在AndroidStudio下NDK的開發(fā)。希望能給從其它IDE遷移到IntelliJ IDEA系開發(fā)或許剛接觸AndroidStudio一些啟發(fā)。所以,這里先留個伏筆。

ndk-build、Android.mk與Application.mk

雖然,已經(jīng)成功的將samples下的hell-jni項(xiàng)目成功編譯出了.so動態(tài)庫。在整個交叉編譯過程中,涉及到了三個比較重要的文件,分別是ndk-build、Android.mkApplication.mk,所以,有必要了解一下,這三個文件在整個交叉編譯過程中起了什么作用。

3、ndk-build

首先,ndk-build是一個shell腳本,目標(biāo)是幫助你正確的調(diào)用NDK的構(gòu)建腳本。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

用法:在項(xiàng)目的根目錄下,執(zhí)行ndk-build腳本命令。

例如:


進(jìn)到hello-jni的項(xiàng)目根目錄執(zhí)行ndk-build,ANDROID_NDK_ROOT是在Cygwin中配置NDK環(huán)境變量的名稱。

$ <ndk>/ndk-build -C <PROJECT_PATH>

用法:在任意目錄下執(zhí)行ndk-build,用-C來指定要編譯的項(xiàng)目的目錄。

例如:


實(shí)際上:執(zhí)行ndk-build相當(dāng)于執(zhí)行了以下命令:

$GNUMAKE -f <ndk>/build/core/build-local.mk
<parameters>

例如:


3.2 ndk-build可選參數(shù)
$ ndk-build clean
清除編譯生成的二進(jìn)制文件。

$ ndk-build -C <project>
指定項(xiàng)目路徑

$ ndk-build NDK_DEBUG=0
NDK_DEBUG為0是編譯為release版,為debug版。

更多的ndk-build的參數(shù)介紹,請參考上面貼出的ndk-build的官方指南。

4、Android.mk

Android.mk是用來向編譯系統(tǒng)指定項(xiàng)目中C/C++源代碼文件編譯、鏈接規(guī)則的一種描述文件。它是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為例,看看都定義了哪些內(nèi)容。打開<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,它用來在開發(fā)的樹文件夾中定位文件的。my-dir是由編譯系統(tǒng)提供的宏,用來返回當(dāng)前目錄的路徑。(注意:這個路徑是包含了Android.mk的路徑)

include $(CLEAR_VARS)
CLEAR_VARS變量也是由編譯系統(tǒng)提供的,include $(CLEAR_VARS)是引用一個特殊的GUN makefile文件,這個makefile文件所做的就是清除定義了很多LOCAL_XXX(例如: LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等)這種格式的變量,但這里不會清除LOCAL_PATH變量。這么做是很有必要的,因?yàn)榫幾g系統(tǒng)解析這些編譯控制文件都是在單一的GUN make上下文環(huán)境中,解析出來的LOCAL_XXX變量都是全局的。

LOCAL_MODULE := hello-jni
LOCAL_MODULE必須是唯一的,且不能包含空格。編譯系統(tǒng)會根據(jù)這個名字生成相應(yīng)的共享庫,并自動添加前綴和后綴。本例中,最終生成的共享庫的名稱為libhello-jni.so。

注意:編譯生成的共享庫都是lib開頭的,如果,你聲明的名稱已經(jīng)包含lib(libhello-jni),那么,最終生成的共享庫名稱就不添加lib前綴了,生成的仍為libhello-jni.so

LOCAL_SRC_FILES := hello-jni.c
LOCAL_SRC_FILES用來指明要編進(jìn)共享庫(.so)的C/C++源文件的列表。

注意:這里只需要指定要編譯的.c或者.cpp等源文件即可,不需要指定.h頭文件。

include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY變量是由系統(tǒng)提供,include $(BUILD_SHARED_LIBRARY)會引用一個Gun makefile腳本,用來收集你定義的LOCAL_XXX格式的變量。同時,決定哪些要編譯,如何編譯等。

注意:顯然BUILD_SHARED_LIBRARY是用來編譯出共享庫.so文件的,同理,也可以使用BUILD_STATIC_LIBRARY來編譯出靜態(tài)庫.a文件。

以上便是編寫一個簡單的Android.mk文件的所有元素。通過上面的描述發(fā)現(xiàn),如果我們要編寫一個自己的Android.mk文件,沒有特殊需求的話,可以直接將hello-jni工程中的Android.mk文件拷貝,然后,修改庫的名稱(LOCAL_MODULE)和要編譯的源文件列表(LOCAL_SRC_FILES)變量即可。

4.2 Android.mk高級

這里來詳細(xì)了解下Android.mk其他的一些變量及語法規(guī)則。

(1)NDK變量與宏
Android.mk中還有一些其他變量,是作為NDK編譯系統(tǒng)的保留變量,你只能依賴它或者定義它。這些變量的規(guī)則如下:

  • LOCAL_開頭的變量名稱(如:LOCAL_MODULELOCAL_PATH等)
  • PRIVATE_NDK_,APP開頭的變量名稱(編譯系統(tǒng)內(nèi)部使用)
  • 小寫的名稱(例如:my-dir,同樣,也是作為內(nèi)部使用)

如果你需要在Android.mk中定義自己的變量,推薦用MY_作為前綴。

(2)NDK定義的變量
CLEAR_VARS
上面已經(jīng)介紹過了,這里就不在贅述。記住一點(diǎn),在定義LOCAL_XXX前,必須引用這個腳本。用法:

include $(CLEAR_VARS)

BUILD_SHARED_LIBRARY
該變量指向了一個腳本,這個腳本會收集你在每個模塊定義的LOCAL_XXX變量信息,并且這個變量還確定了怎樣使用你的源碼去編譯一個共享庫。注意,使用這個變量需要你至少已經(jīng)定義了LOCAL_MODULELOCAL_SRC_FILES。該變量會使編譯系統(tǒng)生成一個以.so結(jié)尾的庫。用法:

include $(BUILD_SHARED_LIBRARY)

BUILD_STATIC_LIBRARY
該變量是BUILD_SHARED_LIBRARY的一個變體,是用來生成一個靜態(tài)庫。構(gòu)建系統(tǒng)并不會把靜態(tài)庫包含進(jìn)你的工程里面,但是可以利用靜態(tài)庫生成共享庫。該變量會使編譯系統(tǒng)生成一個以.a結(jié)尾的庫。

include $(BUILD_STATIC_LIBRARY)

PREBUILT_SHARED_LIBRARY
指向一個腳本,這個腳本被用來指定一個預(yù)構(gòu)建的共享庫。與BUILD_SHARED_LIBRARYBUILD_STATIC_LIBRARY不同,LOCAL_SRC_FILES的值不能是一個源文件,它必須是一個單獨(dú)的指向預(yù)構(gòu)建的共享庫的路徑,例如:foo/libfoo.so。用法:

PREBUILT_STATIC_LIBRARY
該變量與PREBUILT_SHARED_LIBRARY相同,只是指向的一個預(yù)構(gòu)建的靜態(tài)庫。

TARGET_ARCH
這個變量是目標(biāo)CPU架構(gòu)的名字,就像Android Open Source Project里面指定了目標(biāo)CPU架構(gòu)。這個變量用于任意的ARM兼容的構(gòu)建,或者ARM,或者是獨(dú)立于CPU結(jié)構(gòu)的修訂,或者ABI。

TARGET_PLATFORM
編譯到目標(biāo)平臺的api等級。例如,Android5.1對應(yīng)的是Android api22。用法:

TARGET_PLATFORM := android-22

TARGET_ARCH_ABI
當(dāng)編譯系統(tǒng)解析Android.mk文件的時候,這個變量存儲CPU和架構(gòu)的名字。你可以指定一個或者多個下面列出的名字,使用空格分隔兩個名字


用法:

TARGET_ARCH_ABI := arm64-v8a

注意:android-ndk-1.6_r1之前這個這個變量被定義為arm。

TARGET_ABI
該變量將API的級別和ABI聯(lián)系在一起,當(dāng)你在真機(jī)上調(diào)試系統(tǒng)的時候特別有用。用法:

TARGET_ABI := android-22-arm64-v8a

LOCAL_MODULE_FILENAME
這是一個可選變量。允許你重新指定一個變量的名稱來覆蓋默認(rèn)生成的名稱。例如:強(qiáng)制生成libnewfoo.so

LOCAL_MODULE := foo
LOCAL_MODULE_FILENAME := libnewfoo

LOCAL_MODULE_FILENAME不需要指定文件路徑或擴(kuò)展名

LOCAL_SRC_FILES:
該變量用來指定要編譯的源文件列表。這里推薦使用相對路徑。用法:

LOCAL_SRC_FILES := foo1.c \
../Module2/foo2.c

注意:使用Unix風(fēng)格的/,多個文件使用\換行,注意\后面沒有空格。

LOCAL_CPP_EXTENSION
同樣,LOCAL_CPP_EXTENSION也是一個可選變量。用來指定C++源文件的擴(kuò)展名。默認(rèn)是.cpp。從NDK r7版本后,可以指定一系列的擴(kuò)展名。用法:

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++源代碼時,能給編譯器傳遞一個編譯標(biāo)志集合。用來指定附加宏的定義和編譯選項(xiàng)是很有用的。

LOCAL_CPPFLAGS
可選變量,在編譯C++源代碼時(只編譯C++),能給編譯器傳遞一個編譯標(biāo)志集合。

LOCAL_STATIC_LIBRARIES:
該變量定義了本模塊編譯鏈接過程中用到的靜態(tài)庫列表。

LOCAL_SHARED_LIBRARIES
該變量定義了本模塊編譯鏈接過程中用到的共享庫列表。

LOCAL_WHOLE_STATIC_LIBRARIES
該變量跟LOCAL_STATIC_LIBRARIES類似,不同的是在編譯鏈接過程中會載入靜態(tài)庫的所有源代碼目標(biāo)文件。這在解決幾個庫之間循環(huán)引用時,非常有用??梢酝ㄟ^GUN的--whole-archive標(biāo)志來查看相關(guān)說明。

LOCAL_LDLIBS
該變量用來定義本模塊編譯時用到的附加的鏈接器選項(xiàng)。用-l前綴指定。例如:要鏈接/system/lib/libz.so

LOCAL_LDLIBS := -lz

注意:如果,你編譯一個靜態(tài)庫是定義了該變量,編譯系統(tǒng)會忽略它,并且ndk-build會打印一個警告。

LOCAL_LDFLAGS
該變量定義了在編譯給編譯系統(tǒng)傳遞一些其他的鏈接標(biāo)志。用法:

LOCAL_LDFLAGS += -fuse-ld=bfd

注意:如果,你編譯一個靜態(tài)庫是定義了該變量,編譯系統(tǒng)會忽略它,并且ndk-build會打印一個警告。

LOCAL_ALLOW_UNDEFINED_SYMBOLS
默認(rèn)情況下,在編譯一個共享庫的時候,任何未定義的引用,將會拋出"符號未定義(undefined symbol)"的錯誤。該變量能幫助你捕捉代碼中的bug。如果,要禁用這項(xiàng)檢查可以把該變量設(shè)置為true。這么設(shè)置

注意:如果,你編譯一個靜態(tài)庫是定義了該變量,編譯系統(tǒng)會忽略它,并且ndk-build會打印一個警告。

LOCAL_ARM_MODE
默認(rèn)情況下,編譯系統(tǒng)會在ARM平臺"thumb"模式下生成16位的二進(jìn)制文件。定義該變量則強(qiáng)制生成32位"arm"模式下的對象文件。例如:

LOCAL_ARM_MODE := arm

你也可以加上.arm后綴來告訴編譯系統(tǒng),只想對某個源文件使用arm指令。例如:

LOCAL_SRC_FILES := foo.c bar.c.arm

LOCAL_ARM_NEON
只有當(dāng)目標(biāo)平臺為armeabi-v7a指令集時,才定義它。它允許你的C/C++代碼中使用ARM高級指令。也可以在匯編文件中使用NEON指令。你可以使用.neon后綴來指定編譯器支持NEON指令編譯。例如:

LOCAL_SRC_FILES = foo.c.neon bar.c zoo.c.arm.neon

注意:不是所有的ARMv7架構(gòu)的CPU都支持NEON擴(kuò)展。

LOCAL_DISABLE_NO_EXECUTE
Android NDK r4版本開始支持這種"NX bit"的安全功能。默認(rèn)是啟用的,你也可以設(shè)置該變量的值為true來禁用它。但不推薦這么做。該功能不會修改ABI,只在ARMv6+CPU的設(shè)備內(nèi)核上啟用。

LOCAL_DISABLE_RELRO
默認(rèn)情況下,NDK編譯代碼是只讀重定位和GOT保護(hù)的。這個會指示運(yùn)行時鏈接器標(biāo)記特定的內(nèi)存區(qū)是只讀的,在移動位置之后。這樣會使得某些安全漏洞(如GOT覆蓋)更難執(zhí)行。默認(rèn)是啟用的,你也可以把該變量的值設(shè)為true來禁用。但不推薦這么做。

LOCAL_DISABLE_FORMAT_STRING_CHECKS
默認(rèn)情況下,編譯系統(tǒng)編譯代碼時會檢查格式化字符串,如果printf樣式的函數(shù)使用了非常嚴(yán)格的字符串,那么編譯出錯。默認(rèn)是開啟的,你也可以通過設(shè)置該變量的值為true來禁用。但不推薦這么做。

LOCAL_EXPORT_CFLAGS
該變量是用來記錄C/C++編譯器標(biāo)志集合,這些編譯器標(biāo)志會被添加到通過變量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"標(biāo)志將一起傳遞給編譯器。

LOCAL_EXPORT_CPPFLAGS
該變量與LOCAL_EXPORT_CFLAGS類似,但只適用于C++。

LOCAL_EXPORT_C_INCLUDES
該變量與LOCAL_EXPORT_CFLAGS類似,但是該變量用來包含路徑的。例如,上例中bar.c需要包含foo模塊的頭文件。

LOCAL_EXPORT_LDFLAGS:
該變量與LOCAL_EXPORT_CFLAGS類似,但是它用作鏈接器標(biāo)志。

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的系統(tǒng)日志庫。

LOCAL_SHORT_COMMANDS
當(dāng)你的模塊有很多源代碼文件或依賴很多靜態(tài)庫或者共享庫時,設(shè)置為true。這樣會強(qiáng)制編譯系統(tǒng)使用@語法來包含中間對象或鏈接庫來生成歸檔文件。

注意:這個功能在Windows上很有用,因?yàn)閃indows上的命令行支持的最大字符數(shù)為8191個,這對于復(fù)雜的項(xiàng)目來說太小。默認(rèn)是不推薦啟用這個功能,因?yàn)樗鼤咕幾g變得很慢。

LOCAL_THIN_ARCHIVE
編譯靜態(tài)庫是,如果該變量值設(shè)為true,會生成一個較小的歸檔文件。并不包含目標(biāo)文件,而是用目標(biāo)文件的路徑替代。有效值是true,false和空。

注意:如果該模塊不是編譯為靜態(tài)塊,或者預(yù)編譯靜態(tài)庫,該值將被忽略。

LOCAL_FILTER_ASM
該變量的值將作為一個Shell命令,它會過濾從LOCAL_SRC_FILES生成的文件或匯編文件。定義該變量將會發(fā)生下面的情況:

  • 編譯系統(tǒng)會將C/C++源文件生成臨時的匯編文件,而不是將他們編譯到目標(biāo)文件中。
  • 編譯系統(tǒng)會對匯編文件和LOCAL_SRC_FILES中列出的文件執(zhí)行LOCAL_FILTER_ASM中的Shell命令,生成另外一個匯編文件。
  • 編譯系統(tǒng)將這些過濾后的匯編文件編譯進(jìn)目標(biāo)文件。

NDK提供的函數(shù)宏
NDK提供了GNU Make的函數(shù)宏。使用$(call <function>)的方式調(diào)用,它們會返回相應(yīng)的文本信息。
my-dir
該宏返回的是最后包含的makefile文件路徑,一般是當(dāng)前Android.mk的路徑。my-dir對于Android.mk開頭定義的LOCAL_PATH變量很有用。例如:

LOCAL_PATH := $(call my-dir)

由于GNU Make的工作方式,這個宏返回的是構(gòu)建系統(tǒng)在解析構(gòu)建腳本時包含的最后一個makefile的路徑。因此,你不應(yīng)該在include其他的文件之后再繼續(xù)使用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的調(diào)用將LOCAL_PATH的值設(shè)置為了$PATH/foo,因?yàn)?code>$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

如果這種方式不可行,那么可以將第一次調(diào)用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
該宏返回的是當(dāng)前my-dir路徑下的所有子目錄中的Android.mk文件的列表。你可以使用此函數(shù)向構(gòu)建系統(tǒng)提供深層嵌套的源目錄層次結(jié)構(gòu)。默認(rèn)情況下,NDK僅查找包含Android.mk文件的目錄中的文件。

this-makefile
該宏返回的是當(dāng)前makefile文件的路徑(從構(gòu)建系統(tǒng)調(diào)用這個函數(shù)中)。

parent-makefile
該宏返回當(dāng)前目錄樹中的父makefile的路徑(包含當(dāng)前makefile的makefile路徑)。

grand-parent-makefile
該宏返回包含樹中的祖父類makefile的路徑(包含當(dāng)前makefile的makefile的路徑)。

import-module
該宏允許你通過模塊的名稱找到并包含模塊的Android.mk文件。例如:

$(call import-module,<name>)

在這個示例中,構(gòu)建系統(tǒng)會根據(jù)NDK_MODULE_PATH這個環(huán)境變量所指示的目錄里面尋找名為<name>的模塊,然后自動為你include對應(yīng)的Android.mk文件。

5、Application.mk

通過上面的介紹,大體了解了Android.mk文件的用法及規(guī)則。但通常編譯本地C/C++代碼光有Android.mk還不夠,還得需要Application.mk文件。Application.mk也是一種makefile文件,跟Android.mk有相似之處。如果說,Android.mk用來描述單獨(dú)某個模塊的編譯規(guī)則的描述文件,那么Application.mk則是描述整個應(yīng)用程序的模塊的描述文件。Application.mk文件一般在$project-path/jni/Application.mk下($project-path是項(xiàng)目根目錄)。當(dāng)然,也可以放在$NDK/apps/<myapp>/Application.mk路徑下。這兩種方式,造成Application.mk也有細(xì)微的區(qū)別。

(1)變量
APP_PROJECT_PATH
該變量用來指定項(xiàng)目根目錄的絕對路徑。

注意:假如Application.mk文件的路徑是$NDK/apps/<myapp>/Application.mk,那么該變量為強(qiáng)制定義的。如果,Application.mk文件在$project-path/jni/Application.mk路徑下,則是可選變量。

APP_OPTIM:
該變量為可選變量,值為releasedebug。編譯應(yīng)用程序模塊的時,可以用來改變優(yōu)化級別。默認(rèn)是release模式,并且會生成高度優(yōu)化的二進(jìn)制文件。debug模式生成的是未優(yōu)化的二進(jìn)制代碼,但更容易調(diào)試。

APP_CFLAGS
在編譯任何模塊的任何C/C++代碼時,構(gòu)建系統(tǒng)會通過該變量給編譯器傳遞一個C編譯標(biāo)志集合。你可以使用該變量根據(jù)應(yīng)用程序中給定的模塊的需要來改變其構(gòu)建,而不需要修改Android.mk文件本身了。
這些標(biāo)志的所有路徑必須相對于NDK的頂級目錄。例如:如果你有以下設(shè)置:

sources/foo/Android.mk
sources/bar/Android.mk

在編譯期間,你需要在foo/Android.mk中指定你要添加的foo的源路徑,你應(yīng)該使用:

APP_CFLAGS += -Isources/bar

或者:

APP_CFLAGS += -I$(LOCAL_PATH)/../bar

使用-I../bar將不能正常工作,因?yàn)樗葍r于-I$NDK_ROOT/../bar。

注意:在android-ndk-1.5_r1版本中,該變量只適用于C,不能作用于C++。之后的所有的版本,該變量可適用C/C++源碼上。

APP_CPPFLAGS
該變量包含一組C++編譯器標(biāo)志,構(gòu)建系統(tǒng)僅在構(gòu)建C++源代碼時傳遞給編譯器。

APP_LDFLAGS
在鏈接的時候,構(gòu)建系統(tǒng)系統(tǒng)會想鏈接器傳遞一組鏈接標(biāo)志。該變量僅在構(gòu)建系統(tǒng)構(gòu)建共享庫和可執(zhí)行文件的時候才生效,當(dāng)構(gòu)建靜態(tài)庫時,將被忽略。

APP_BUILD_SCRIPT
默認(rèn)情況下,NDK構(gòu)建系統(tǒng)會在$project-path/jni/目錄下查找Android.mk文件。如果你想修改此行為,你可以定義APP_BUILD_SCRIPT變量,并指向備用的構(gòu)建腳本。編譯系統(tǒng)總是將一個非絕對路徑解釋為NDK的頂級目錄。

APP_ABI
默認(rèn)情況下,編譯系統(tǒng)會根據(jù)armeabi ABI生成機(jī)器碼。該機(jī)器碼基于ARMv5TE并且支持浮點(diǎn)運(yùn)算的CPU。你可以使用APP_ABI參數(shù)來指定不同的ABI。不同的指令集的APP_ABI設(shè)置如下:

注意:all是android-ndk-r7版本開始支持的。
你也可以指定多個值,將它們放在同一行,中間用空格隔開。例如:

APP_ABI := armeabi armeabi-v7a x86 mips

APP_PLATFORM
此變量包含目標(biāo)Android的名稱。例如:android-3對應(yīng)的是Android1.5的系統(tǒng)鏡像。

APP_STL
默認(rèn)情況下,NDK構(gòu)建系統(tǒng)只為最小的C++運(yùn)行庫(/system/lib/libstdc++.so)提供C++頭文件。此外,你還可以在自己的應(yīng)用程序中使用或鏈接其他C++實(shí)現(xiàn)。可以使用APP_STL來選擇其中的一個。例如:

APP_STL := stlport_static
APP_STL := stlport_shared
APP_STL := system

APP_SHORT_COMMANDS
該變量相當(dāng)于整個項(xiàng)目的Android.mk中定義了LOCAL_SHORT_COMMANDS。

NDK_TOOLCHAIN_VERSION
將此變量定義為4.9版本的GCC編譯器。在android-ndk-r13默認(rèn)是Clang編譯器。

APP_PIE
從Android 4.1(API級別16)開始,Android的動態(tài)鏈接器支持位置無關(guān)可執(zhí)行文件(PIE)。從Android 5.0(API級別21),可執(zhí)行文件需要PIE。要使用PIE構(gòu)建可執(zhí)行文件,需設(shè)置-fPIE標(biāo)志。這個標(biāo)志會使得通過隨機(jī)代碼的位置來查找內(nèi)存損壞的bug更加困難。默認(rèn)情況下,如果您的項(xiàng)目目標(biāo)為Android-16或更高版本,ndk-build會自動將此值設(shè)置為true。您可以將其手動設(shè)置為true或false。

注意:此標(biāo)志僅適用于可執(zhí)行文件。它在構(gòu)建共享或靜態(tài)庫時沒有任何作用。

APP_THIN_ARCHIVE
相當(dāng)于在Android.mk文件中為此項(xiàng)目中的所有靜態(tài)庫模塊設(shè)置LOCAL_THIN_ARCHIVE的默認(rèn)值。

以上內(nèi)容可能不一定完全正確,大部分內(nèi)容是根據(jù)Android官方文檔,通過自己的理解轉(zhuǎn)述的。并沒有每個變量在.mk文件中有實(shí)踐過。所以,如果遇到你覺得有問題的地方,歡迎與我交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容