編輯:Android游戲開發
承接上篇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)安裝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中。如圖:
NDK編譯的環境有很多,但基本都是通過ndk-build
工具來完成的。有直接通過Eclipse安裝CDT即可完編譯,也可以通過安裝Cygwin來編譯。事實上,在android-ndk-r7
之後的版本,已經不需要安裝Cygwin就可以編譯出.so
了。但這裡還是想介紹下,因為筆者開發曾用Cygwin編譯過一段時間,而且多了解一種編譯途徑也沒什麼壞處。當然,熟悉Linux平台或者Mac平台開發的朋友會感覺更親切些。
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項目的權限修改為可寫入、可修改等。
(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
工程了。
這裡就不花篇幅介紹這相關的內容,下一篇專門介紹下AndroidStudio的使用及在AndroidStudio下NDK的開發。希望能給從其它IDE遷移到IntelliJ IDEA系開發或許剛接觸AndroidStudio一些啟發。所以,這裡先留個伏筆。
雖然,已經成功的將samples
下的hell-jni
項目成功編譯出了.so
動態庫。在整個交叉編譯過程中,涉及到了三個比較重要的文件,分別是ndk-build
、Android.mk
和Application.mk
,所以,有必要了解一下,這三個文件在整個交叉編譯過程中起了什麼作用。
首先,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
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>
例如:
$ ndk-build clean 清除編譯生成的二進制文件。 $ ndk-build -C <project> 指定項目路徑 $ ndk-build NDK_DEBUG=0 NDK_DEBUG為0是編譯為release版,為debug版。
更多的ndk-build
的參數介紹,請參考上面貼出的ndk-build
的官方指南。
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
首先,仍以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
)變量即可。
這裡來詳細了解下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生成的文件或匯編文件。定義該變量將會發生下面的情況:
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文件。
通過上面的介紹,大體了解了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
文件中有實踐過。所以,如果遇到你覺得有問題的地方,歡迎與我交流。