Android NDK編程淺入深出之--Android.mk
Android.mk
Android.mk是一個向Android NDK構建系統描述NDK項目的GUN Makefile片段。它是每一個NDK項目的必備組件。構建系統希望它出現在jni子目錄中。下面是hello-jni項目中Android.mk檔案的內容。
- # Copyright (C) 2009 The Android Open Source Project
- #
- # Licensed under the Apache License, Version 2.0 (the License);
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an AS IS BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- #
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := hello-jni
- LOCAL_SRC_FILES := hello-jni.c
-
- include $(BUILD_SHARED_LIBRARY)
為了更好地理解它的句法,我們逐行分析。因為這個是一個GUN Makefile片段,所以它的句法和其他Makefile是一樣的。每行都包含一個單獨的指令,以“#”開頭的是注釋行,GUN Make 工具不處理它們。根據命名規範,變數名要大寫。
註解區塊後的第一條指令是用來定義LOCAL_PATH變數的。根據Android構建系統的要求,Android.mk文檔必須以LOCAL_PATH變數的定義開頭。
- LOCAL_PATH :=$(call my-dir)
Android構建系統利用LOCAL_PATH來定位源檔案。因為將該變數設定為寫入程式碼值並不合適,所以Android構建系統提供了一個名為my-dir的宏功能。通過將該變數設定為my-dir 宏功能的傳回值,可以將其放在目前的目錄下。
Android構建系統將CLEAR_VARS變數設定為clear-vars.mk片段的位置。包含Makefile片段可以清除除了LOCAL_PATH以外的LOCAL_變數,例如LOCAL_MODULE與LOCAL_SRC_FILES等。
- Include $(CLEAR_VARS)
這樣做是因為Android構建系統在單次執行中解析多個構建檔案和模組定義,而LOCAL_是全域變數。清除它們可以避免衝突,每一個原生組件被稱為一個模組。
LOCAL_MODULE變數用來給這些模組設定一個唯一的名稱。下面的代碼將該模組的名稱設為hello-jni:
- LOCAL_MODULE := hello-jni
因為模組名稱也被用於給構建過程所產生的檔案命名,所以構建系統給該檔案添加了適當的首碼和尾碼。本例中,hello-jni模組會產生一個共用庫檔案且構建系統會將它命名為libhello-jni.so。
用LOCAL_SRC_FILES變數定義用來建立和組裝這個模組的源檔案清單。
- LOCAL_SRC_FILES := hello-jni.c
這裡,hello-jni模組只由一個源檔案產生,而LOCAL_SRC_FILES變數可以包含用空格分開的多個源檔案名稱。
至此,Android.mk檔案中定義的構建系統變數簡單描述了原生項目。編譯和產生實際模組的構建系統還需要包含合適的構建系統片段,具體需要包含哪些片段取決於想要產生模組的類型。
1. 構建共用庫
為了建立可供主應用程式使用的模組,必須將該模組變成共用庫。Android NDK構建系統將BUILD_SHARED_LIBRARY變數設定成build-shared-library.mk檔案的儲存位置。該Makefile片段包含了將源檔案構建和組裝成共用庫的必要過程:
- include $(BUILD_SHARED_LIBRARY)
hello-jni是一個簡單的模組;然而,除非你的模組需要特殊處理,否則Android.mk文檔將會包含一模一樣的流程和指令。
2. 構建多個共用庫
基於不同的應用程式的體繫結構,一個單獨的Android.mk文檔可能產生多個共用庫模組。如程式:
- LOCAL_PATH := $(call my-dir)
-
- #
- #模組 1
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := module1
- LOCAL_SRC_FILES := module1.c
- include $(BUILD_SHARED_LIBRARY)
-
- #
- #模組 2
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := module2
- LOCAL_SRC_FILES := module2.c
-
- include $(BUILD_SHARED_LIBRARY)
在處理完這個Android.mk構建文檔之後,Android NDK構建系統會產生libmodule1.so和libmodule2.so兩個共用庫。
3. 構建靜態庫
Android NDK構建系統也支援靜態庫。實際的Android應用程式並不直接使用靜態庫,並且應用程式套件組合中也不包含靜態庫。靜態庫可以用來構建共用庫。例如,在將第三方代碼添加到現有原生項目中時,不用直接將第三方原始碼包括在原生項目中,而是將第三方代碼編譯成靜態庫然後併入共用庫,如程式:
- LOCAL_PATH := $(call my-dir)
-
- #
- # 第三方AVI庫
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := avilib
- LOCAL_SRC_FILES := avilib.c platform_posix.c
-
- include $(BUILD_STATIC_LIBRARY)
-
- #
- # 原生模組
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := module
- LOCAL_SRC_FILES := module.c
-
- LOCAL_STATIC_LIBRARIES := avilib
-
- include $(BUILD_SHARED_LIBRARY)
在將第三方代碼模組產生靜態庫之後,共用庫就可以通過將它的模組名添加到LOCAL_STATIC_LIBRARIES變數中來使用該模組。
4. 用共用程式庫共用通用模組
靜態庫可以保證原始碼模組化;但是,當靜態庫與共用庫相連時,它就變成了共用庫的一部分。在多個共用庫的情況下,多個共用庫與同一個靜態庫串連時,需要將通用模組的多個副本與不同共用庫重複相連,這樣就增加了應用程式的大小。在這種情況下,不用構建靜態庫,而是將通用模組作為共用庫建立起來,而動態串連相依模組以便消除重複的副本,如程式:
- LOCAL_PATH := $(call my-dir)
-
- #
- # 第三方AVI庫
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := avilib
- LOCAL_SRC_FILES := avilib.c platform_posix.c
-
- include $(BUILD_SHARED_LIBRARY)
-
- #
- # 原生模組 1
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := module1
- LOCAL_SRC_FILES := module1.c
-
- LOCAL_SHARED_LIBRARIES := avilib
-
- include $(BUILD_SHARED_LIBRARY)
-
- #
- # 原生模組 2
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := module2
- LOCAL_SRC_FILES := module2.c
-
- LOCAL_SHARED_LIBRARIES := avilib
-
- include $(BUILD_SHARED_LIBRARY)
5. 在多個NDK項目間共用模組
同時使用靜態庫和共用庫時,可以在模組間共用通用模組。但要說明的是,所有這些模組必須屬於同一個NDK項目。從R5版本開始,Android NDK也允許在NDK項目間共用和重用模組。考慮前面講過的樣本,可以通過以下步驟在多個NDK項目間共用avilib模組:
首先,將avilib原始碼移動到NDK項目以外的位置,例如:C:androidshared-modules avilib。為了避免命名衝突,目錄結構也可以包含模組提供者的名字,例如:C: androidshared-modules ranscodeavilib。
注意:
在Android NDK構建系統中,共用模組路徑不能包含空格。
作為共用模組,avilib需要自己的Android.mk檔案,如程式:
- LOCAL_PATH := $(call my-dir)
-
- #
- #第三方AVI庫
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := avilib
- LOCAL_SRC_FILES := avilib.c platform_posix.c
-
- include $(BUILD_SHARED_LIBRARY)
現在,可以將avilib模組從NDK項目的Android.mk檔案中移除。為了使用這個共用模組,將以transcode/avilib為參數調用函數宏import-module部分添加在構建文檔的末尾。為了避免構建系統的衝突,應該將import-module函數宏調用放在Android.mk文檔的末尾。如程式:
- #
- # 原生模組
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := module
- LOCAL_SRC_FILES := module.c
- LOCAL_SHARED_LIBRARIES := avilib
-
- include $(BUILD_SHARED_LIBRARY)
-
- $(call import-module,transcode/avilib)
import-module函數宏需要先定位共用模組,然後再將它匯入到NDK項目中。預設情況下,import-module函數宏只搜尋/sources目錄。為了搜尋c:androidshared-modules目錄,定義一個名為NDK_MODULE_PATH的新環境變數並將它設定成共用模組的根目錄,例如:c:androidshared-modules。
6. 用Prebuilt庫
使用共用模組要求有共用模組的原始碼,Android NDK構建系統簡單地把這些源檔案包含在NDK項目中並每次構建它們。自R5版本以後,Android NDK也提供對Prebuilt庫的支援。在下面的情況下,Prebuilt庫是非常有用的:
想在不發布原始碼的情況下將你的模組發布給他人。
想使用共用模組的預建版來加速構建過程。
儘管已經被編譯了,但預建模組仍需要一個Android.mk構建文檔,如程式:
- LOCAL_PATH := $(call my-dir)
-
- #
- # 第三方預構建AVI庫
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := avilib
- LOCAL_SRC_FILES := libavilib.so
-
- include $(PREBUILT_SHARED_LIBRARY)
LOCAL_SRC_FILES變數指向的不是源檔案,而是實際Prebuilt庫相對於LOCAL_PATH的位置。
注意:
Prebuilt庫定義中不包含任何關於該庫所構建的實際機器體繫結構的資訊。開發人員需要確保Prebuilt庫是為與NDK項目相同的機器體繫結構而構建的。
PREBUILT_SHARED_LIBRARY變數指向prebuilt-shared-library.mk Makefile片段。它什麼都沒有構建,但是它將Prebuilt庫複製到了NDK項目的libs目錄下。通過使用PREBUILT_STATIC_LIBRARY變數,靜態庫可以像共用庫一樣被用作Prebuilt庫,NDK項目可以像普通共用庫一樣使用Prebuilt庫了。
- ...
- LOCAL_SHARED_LIBRARIES :=avilib
- …
7. 構建獨立的可執行檔
在Android平台上使用原生組件的推薦和支援的方法是將它們打包成共用庫。但是,為了方便測試和進行快速原型設計,Android NDK也支援構建獨立的可執行檔。這些獨立的可執行檔是不用打包成APK檔案就可以複製到Android裝置上的常規Linux應用程式,而且它們可以直接執行,而不通過Java應用程式載入。產生獨立可執行檔需要在Android.mk構建文檔中匯入BUILD_EXECUTABLE變數,而不是匯入BUILD_SHARED_ LIBRARY變數,如程式:
- #
- # 獨立的可執行檔原生模組
- #
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := module
- LOCAL_SRC_FILES := module.c
-
- LOCAL_STATIC_LIBRARIES := avilib
-
- include $(BUILD_EXECUTABLE)
BUILD_EXECUTABLE變數指向build-executable.mk Makefile片段,該片段包含了在Android平台上產生獨立可執行檔的必要步驟。獨立可執行檔以與模組相同的名稱被放在libs/目錄下。儘管放在該目錄下,但在打包階段它並沒有被包含在APK檔案中。
8. 其他構建系統變數
除了在前幾節提到的變數之外,Android NDK構建系統還支援其他變數,本節將對這些變數進行簡要說明。
構建系統定義的變數有:
TARGET_ARCH:目標CPU體繫結構的名稱,例如arm
TARGET_PLATFORM:目標Android平台的名稱,例如:android-3
TARGET_ARCH_ABI:目標CPU體繫結構和ABI的名稱,例如:armeabi-v7a
TARGET_ABI:目標平台和ABI的串聯,例如:android-3-armeabi-v7a
可被定義為模組說明部分的變數有:
LOCAL_MODULE_FILENAME:可選變數,用來重新定義產生的輸出檔案名稱。預設情況下,構建系統使用LOCAL_MODULE的值作為產生的輸出檔案名稱,但變數LOCAL_MODULE_ FILENAME可以覆蓋LOCAL_MODULE的值。
LOCAL_CPP_EXTENSION:C++源檔案的預設副檔名是.cpp。這個變數可以用來為C++原始碼指定一個或多個副檔名。
- …
- LOCAL_CPP_ EXTENSION :=.cpp .cxx
- …
LOCAL_CPP_FEATURES:可選變數,用來指明模組所依賴的具體C++特性,如RTTI、exceptions等。
- …
- LOCAL_CPP_FEATURES :=rtti
- …
LOCAL_C_INCLUDES:可選目錄列表,NDK安裝目錄的相對路徑,用來搜尋標頭檔。
- …
- LOCAL_C_INCLUDES :=sources/shared-module
- LOCAL_C_INCLUDES :=$(LOCAL_PATH)/include
- …
LOCAL_CFLAGS:一組可選的編譯器標誌,在編譯C和C++源檔案的時候會被傳送給編譯器。
- …
- LOCAL_CFLAGS :=-DNDEBUG -DPORT=1234
- …
LOCAL_CPP_FLAGS:一組可選的編譯標誌,在只編譯C++源檔案時被傳送給編譯器。
LOCAL_WHOLE_STATIC_LIBRARIES:LOCAL_STATIC_LIBRARIES的變體,用來指明應該被包含在產生的共用庫中的所有靜態庫內容。
(當幾個靜態庫之間有循環相依性時,LOCAL_WHOLE_STATIC_LIBRARIES很有用。)
LOCAL_LDLIBS:連結標誌的可選列表,當對目標檔案進行連結以產生輸出檔案時該標誌將被傳送給連結器。它主要用於傳送要進行動態連結的系統庫列表。例如:要與Android NDK日誌庫連結,使用以下代碼:
- LOCAL_LDFLAGS :=?llog
LOCAL_ALLOW_UNDEFINED_SYMBOLS:選擇性參數,它禁止在產生的檔案中進行缺失符號檢查。若沒有定義,連結器會在符號缺失時建置錯誤資訊。
LOCAL_ARM_MODE:選擇性參數,ARM機器體繫結構特有變數,用於指定要產生的ARM二進位類型。預設情況下,構建系統在拇指模式下用16位指令產生,但該變數可以被設定為arm來指定使用32位指令。
- LOCAL_ARM_MODE :=arm
該變數改變了整個模組的構建系統行為;可以用.arm副檔名指定只在arm模式下構建特定檔案。
- LOCAL_SRC_FILES :=file1.c file2.c.arm
LOCAL_ARM_NEON:選擇性參數,ARM機器體繫結構特有變數,用來指定在源檔案中應該使用的ARM進階單指令流多資料流(Single Instruction Multiple Data,SIMD)(a.k.a. NEON)內嵌函式。
- LOCAL_ARM_NEON :=true
該變數改變了整個模組的構建系統行為;可以用.neon副檔名指定只構建帶有NEON內嵌函式的特定檔案。
- LOCAL_SRC_FILES :=file1.c file2.c.neon
LOCAL_DISABLE_NO_EXECUTE:可選變數,用來禁用NX Bit安全特性。NX Bit代表Never Execute(永不執行),它是在CPU中使用的一項技術,用來隔離代碼區和儲存區。這樣可以防止惡意軟體通過將它的代碼插入應用程式的儲存區來控制應用程式。
- LOCAL_DISABLE_NO_EXECUTE :=true
LOCAL_EXPORT_CFLAGS:該變數記錄一組編譯器標誌,這些編譯器標誌會被添加到通過變數LOCAL_STATIC_LIBRARIES或LOCAL_SHARED_LIBRARIES使用本模組的其他模組的LOCAL_CFLAGS定義中。
- LOCAL_MODULE := avilib
- ...
- LOCAL_EXPORT_CFLAGS := − DENABLE_AUDIO
- ...
- LOCAL_MODULE := module1
- LOCAL_CFLAGS := − DDEBUG
- ...
- LOCAL_SHARED_LIBRARIES := avilib
編譯器在構建module1時會以-DENABLE_AUDIO –DDEBUG標誌執行。
LOCAL_EXPORT_CPPFLAGS:和LOCAL_EXPORT_CLAGS一樣,但是它是C++特定代碼編譯器標誌。
LOCAL_EXPORT_LDFLAGS:和LOCAL_EXPORT_CFLAGS一樣,但用作連結器標誌。
LOCAL_EXPORT_C_INCLUDES:該變數允許記錄路徑集,這些路徑會被添加到通過變數LOCAL_STATIC_LIBRARIES或LOCAL_SHARED_LIBRARIES使用該模組的LOCAL_C_INCLUDES定義中。
LOCAL_SHORT_COMMANDS:對於有大量資源或獨立的靜態/共用庫的模組,該變數應該被設定為true。諸如Windows之類的作業系統只允許命令列最多輸入8 191個字元;該變數通過分解構建命令使其長度小於8 191個字元。在較小的模組中不推薦使用該方法,因為使用它會讓構建過程變慢。
LOCAL_FILTER_ASM:該變數定義了用於過濾來自LOCAL_SRC_FILES變數的裝配檔案的應用程式。
9. 其他的構建系統函數宏
本節概括了Android NDK構建系統支援的其他函數宏。
all-subdir-makefiles:返回目前的目錄的所有子目錄下的Android.mk構建檔案清單。例如,調用以下命令可以將子目錄下的所有Android.mk檔案包含在構建過程中:
- include $(call all-subdir-makefiles)
this-makefile:返回當前Android.mk構建檔案的路徑。
parent-makefile:返回包含當前構建檔案的父Android.mk構建檔案的路徑。
grand-parent-makefile:和parent-makefile一樣但用於祖父目錄。
10. 定義新變數
開發人員可以定義其他變數來簡化他們的構建檔案。以LOCAL_和NDK_首碼開頭的名稱預留給Android NDK構建系統使用。建議開發人員定義的變數以MY_開頭,如程式:
- ...
- MY_SRC_FILES := avilib.c platform_posix.c
- LOCAL_SRC_FILES := $(addprefix avilib/, $(MY_SRC_FILES))
- ...
11. 條件操作
Android.mk構建檔案也可以包含關於這些變數的條件操作,例如:在每個體繫結構中包含一個不同的源檔案集,如程式:
- ...
- ifeq ($(TARGET_ARCH),arm)
- LOCAL_SRC_FILES + = armonly.c
- else
- LOCAL_SRC_FILES + = generic.c
- endif
- ...
Application.mk
Application.mk是Android NDK構建系統使用的一個可選構建檔案。和Android.mk檔案一樣,它也被放在jni目錄下。Application.mk也是一個GUN Makefile片段。它的目的是描述應用程式需要哪些模組;它也定義所有模組的通用變數。以下是Application.mk構建檔案支援的變數:
APP_MODULES:預設情況下,Android NDK構建系統構建Android.mk檔案聲明的所有模組。該變數可以覆蓋上述行為並提供一個用空格分開的、需要被構建的模組列表。
APP_OPTIM:該變數可以被設定為release或debug以改變產生的二進位檔案的最佳化層級。預設情況下使用的是release模式,並且此時產生的二進位檔案被高度最佳化。該變數可以被設定為debug模式以產生更容易調試的未最佳化二進位檔案。
APP_CLAGS:該變數列出了一些編譯器標誌,在編譯任何模組的C和C++源檔案時這些標誌都會被傳給編譯器。
APP_CPPFLAGS:該變數列出了一些編譯器標誌,在編譯任何模組的C++源檔案時這些標誌都會被傳給編譯器。
APP_BUILD_SCRIPT:預設情況下,Android NDK構建系統在項目的jni子目錄下尋找Android.mk構建檔案。可以用該變數改變上述行為,並使用不同的組建檔案。
APP_ABI:預設情況下,Android NDK構建系統為armeabi ABI產生二進位檔案。可以用該變數改變上述行為,並為其他ABI產生二進位檔案,例如:
- APP_ABI := mips
另外,可以設定多個ABI
- APP_ABI := armeabi mips
為所有支援的ABI產生二進位檔案
- APP_ABI := all
APP_STL:預設情況下,Android NDK構建系統使用最小STL運行庫,也被稱為system庫。可以用該變數選擇不同的STL實現。
- APP_STL :=stlport_shared
APP_GNUSTL_FORCE_CPP_FEATURES:與LOCAL_CPP_EXTENSIONS變數相似,該變數表明所有模組都依賴於具體的C++特性,如RTTI、exceptions等。
APP_SHORT_COMMANDS:與LOCAL_SHORT_COMMANDS變數相似,該變數使得構建系統在有大量源檔案的情況下可以在項目中使用更短的命令。