1. 程式人生 > >Android NDK編譯選項設定

Android NDK編譯選項設定

       在Android NDK開發中,有兩個重要的檔案:Android.mk和Application.mk,各盡其責,指導編譯器如何編譯程式,並決定編譯結果是什麼。本文將詳細說明幾個常見的NDK選項的配置,幫助大家理解相應的配置選項。



     一、Application.mk


       Application.mk實際上是輕量級Makefile,通常在$PROJECT/jni目錄下,用於配置所有modules的編譯變數,例子如下:

APP_ABI := armeabi arm64-v8a x86_64 x86 armeabi-v7a
NDK_TOOLCHAIN_VERSION := clang3.5
APP_STL := stlport_static
APP_OPTIM:= debuge


      1、APP_ABI(目標平臺ABI型別)

       NDK編譯中,APP_ABI預設選擇armeabi ABI,可通過設定APP_ABI設定一個或者多個ABI,表一為不同的APP_ABI所對應的指令集。
Instrunction set Value
ARMv5TE based CPU APP_ABI := armeabi
ARMv7 based CPU APP_ABI := armeabi-v7a
ARMv8 AArch64 APP_ABI := arm64-v8a
IA-32 APP_ABI := x86
Intel64 APP_ABI := x86_64
MIPS32 APP_ABI := mips
MIPS64(r6) APP_ABI := mips64
All supported instruction sets APP_ABI := all
表一:ABI型別

       在開發時可根據需求選擇APP_ABI,對於ABI的選擇需要考慮到效率和APK大小。由於armeabi-v7a指令集相容armeabi;市面上的x86手機為了相容性,基本都使用libhoudini模組,相容arm指令集;64位機型預設支援32位abi的so,因此在對大小要求比較高的情況下,可以只選擇市面上裝置基本相容的armeabi ABI,如果對效能有些許要求,可以再新增x86 ABI。


      2、 NDK_TOOLCHAIN_VERSION(編譯器型別、版本)


       預設採用的是GCC編譯器,對於GCC版本的選擇與NDK版本有關係,本人使用的是NDK R12,在64位ABI預設是GCC 4.9,32位ABI預設是GCC 4.8,當然也可以像上面例子中給出的設定一樣,設定clang編譯器。

      3、 APP_STL(執行庫型別)

       Android NDK 預設使用的是最小支援的C++執行庫,如果你需要你的NDK程式中使用STL,則可以設定APP_STL := stlport_static,APP_STL有表二中的幾種取值。
Name Explanation
system(default) 系統預設的C++執行庫
stlport_static 以靜態連結方式使用的sttport版本的STL
stlport_shared 以動態連結方式使用的sttport版本的STL
gnustl_static 以靜態連結方式使用的gnustl版本的STL
gnustl_shared 以動態連結方式使用的gnustl版本的STL
gabi++_static 以靜態連結方式使用的gabi++
gabi++_shared 以動態連結方式使用的gabi++
c++_static 以靜態連結方式使用的LLVM libc++
c++_shared 以動態連結方式使用的LLVM libc++
表二:NDK執行庫

       若APK中有多個SO檔案用到STL,建議都使用動態方式連結STL,這樣可以減小整個APK檔案大小。
另外需要注意的是官方提供的NDK執行庫除了預設的以外都支援RTTI和異常,然而預設是禁用的,將在下面的Android.mk中說明如何開啟。


      4、APP_OPTIM(編譯模式)

       “release”模式為預設的,生成的是優化後的二進位制;也可以設定為“debug”模式,“debug”模式生成的是未優化二進位制,提供很多BUG資訊,便於除錯和分析。
還有其他配置選項,有興趣可以檢視Application.mk官方文件

     二、Android.mk


       Android.mk也是一個輕量級的Makefile,其將C/C++原始碼組織到一個個module中,module可以是靜態庫、共享庫或者獨立的可執行檔案, 一個Android.mk檔案可以有一個,也可以是多個module,modules之間也可以有依賴關係。

      1、基本概念

       Android.mk中包括NDK提供的巨集、變數以及模組描述變數,這些巨集、變數以及變數的賦值共同組成了Android.mk檔案,其在NDK編譯中各盡其責,指導著NDK的編譯。
      巨集:包括my-dir、all-subdir-makefiles等,通過‘$(call <function>)’來呼叫,返回文字資訊。
      變數:包括CLEAR_VARS、BUILD_SHARED_LIBRARY、TARGET_ARCH等,由NDK編譯系統提供,並且在Android.mk檔案被解析前就已經存在。Android.mk檔案有可能被多次解析,因此每次解析時這些變數的值都有可能不同。
      模組描述變數:Module-description,包括LOCAL_PATH、LOCAL_MODULE、LOCAL_SRC_FILES等LOCAL_字首變數,這些變數除LOCAL_PATH外,均填寫在語句include $(CLEAR_VARS)和include $(BUILD_XXX)之間。
其他Android.mk配置可以檢視Android.mk官方文件


      2、基礎

      在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(當前目錄) 
       LOCAL_PATH為模組描述變數,一個Android.mk必須定義LOCAL_PATH,用於定位原始檔,在本例中,使用的是編譯系統提供的巨集“my-dir”(“my-dir”返回最近一次包括Makefile檔案路徑,通常為當前Android.mk所在目錄),用於返回當前目錄。
此變數不會被CLEAR_VARS清除,所以每個Android.mk檔案只需要定義一次就可以了。

       CLEAR_VARS(變數清除)
       CLEAR_VARS變數由編譯系統提供,顧名思義,作用是清除模組變數(在include $(CLEAR_VARS)和include $(BUILD_XXX)之間的LOCAL_XXX模組變數),當然LOCAL_PATH除外。由於所有的編譯控制檔案都是單一的GNU Make可執行上下文環境中解析,而這個上下文環境中所有的變數都是全域性的,所以編譯module前需要清理相應變數。

       LOCAL_MODULE(module名稱)
       LOCAL_MODULE是Android.mk檔案中module的唯一標識,這個名字必須是唯一的,且中間不能有空格。在預設情況下,它決定了生成的檔名,如“hello-jni”對應的動態庫名稱為libhello-jni.so,然而要索引它時,需要“hello-jni”即可,也可以通過變數LOCAL_MODULE_FILENAME來覆蓋這個預設名稱。

       LOCAL_SRC_FILES (原始碼檔案)
       LOCAL_SRC_FILES 變數包括C/C++原始檔列表,這些原始檔會被編譯到一個module中,不過也不必列出標頭檔案和包括檔案,編譯系統會自動為你找打所有需要的依賴關係。值得注意的是linux下路徑使用順斜槓(/)。

       BUILD_SHARED_LIBRARY(動態庫編譯)
       BUILD_SHARED_LIBRARY是編譯器提供的變數,表示編譯成動態庫,它指向一個GNU Makefile指令碼,這個指令碼收集從include $(CLEAR_VARS)後所有的LOCAL_XXX變數中定義的所有資訊,決定編譯什麼以及怎麼編譯。
還有BUILD_STATIC_LIBRARY,和BUILD_SHARED_LIBRARY類似,表示編譯成靜態庫,靜態庫不會被拷貝到APK中。

       PREBUILT_SHARED_LIBRARY(預編譯)
       指向一個編譯指令碼,用來指定一個預編譯動態庫.使用此變數時,不像BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY那樣,LOCAL_SRC_FILES的值必須是隻能有一個指向預編譯動態庫的路徑,如foo/libfoo.so,而不是原始檔。如下栗子。

include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libtest.so
include $(PREBUILT_SHARED_LIBRARY)


PREBUILD_STATIC_LIBRARY和PREBUILD_SHARED_LIBRARY一樣,只不過是用於引用靜態庫。

      TARGET_ARCH_ABI(目標ABI名稱)
      如表一所示,目標ABI名稱。若定義了多個ABI,則每次解析Android.mk時,值都不一樣,主要使用場景為根本不同的ABI定義不同的檔案等。


      3、其他模組變數LOCAL_LDLIBS(連結庫)

      用於額外連結選項,所有的庫都有“-l”字首。可同時列出多個庫,用空格隔開,例如:

LOCAL_LDLIBS := -llog -ldl

       
      Android NDK預設連結了多個庫,不需要顯示的新增到LOCAL_LDLIBS中,包括 the standard C libraries,the standard C++ libraries,real-time extensions和 pthread庫。同時也提供了一些需要顯示新增的庫,這些庫版本有關係,如表三所示。
Android level Lib Explanation
 
Android-3
-llog Android Log
-lz Zlib Compression Library
-ldl Dynamic Linker Library
Android-4 -lGLESv1_CM OpenGL ES 1.x Library
Android-5 -lGLESv2 OpenGL ES 2.0 Library
Android-8 -ljnigraphics The jnigraphics Library
 
Android-9
-lEGL The EGL graphics library
-lOpenSLES Open ES native audio Library
-landroid Natice Android API
Android-14 -lOpenMAXAL OpenMAX AL natice multimedia Library
Android-18 -lGLESv3 OpenGL ES 3.0 Library
Android-21 -lGLESv3 OpenGL ES 3.1 Library
表三:連結庫

        LOCAL_CFLAGS、LOCAL_CPPFLAGS和LOCAL_LDFLAGS(編譯、連結標誌)

        LOCAL_CFLAGS定義的是在編譯C/C++時,傳遞給編譯器的標誌集合,LOCAL_CPPFLAGS只支援C++,作用也是傳遞給編譯器一些資訊,LOCAL_LDFLAGS是指傳遞給聯結器一些額外的引數。

在NDK開發中難免會用到這些標誌位,特別是在優化編譯時,下面的是本人在開發中遇到的編譯選項。

       ① LOCAL_CPPFLAGS += -fexceptions
       由於NDK編譯從R5開始才支援C++異常控制,為了通用性,異常處理預設是禁用的(-fno-exceptions),因此需要在指定module中新增LOCAL_CPPFLAGS += -fexceptions編譯選項方可編譯帶異常處理的C++程式碼。也可以直接在Application.mk中配置APP_CPPFLAGS += -fexceptions。

       ② LOCAL_CPPFLAGS += -frtti
       從NDK R5開始,NDK也開始支援C++ RTTI了,但為了通用性,所有的C++原始檔被構建的時候預設是不支援RTTI的(-fno-rtti),可以通過在Android.mk中新增:LOCAL_CPPFLAGS += -frtti或者在Application.mk新增APP_CPPFLAGS += -frtti來開啟RTTI。

       ③ LOCAL_CFLAGS += -fvisibility=hidden
       在NDK開發中,原始檔的函式都有一個預設的visibility屬性為public,編譯生成的so檔案中幾乎所有的函式名、全域性變數名均被匯出,其實只需要匯出java_com開頭的jni函式即可,其他函式不需要暴露出來,在Android.mk中設定LOCAL_CFLAGS += -fvisibility=hidden,就可以隱藏不需要匯出的函式,若某個函式需要匯出,則新增JNIEXPORT或者__attribute__ ((visibility ("default")))即可。
除了安全,不匯出不必要的函式外,還能減小so體積。

       ④ LOCAL_CFLAGS += -ffunction-sections
       不新增此引數時,編譯檔案.o中程式碼部分只有.text段,使用此引數,會使每個函式單獨有一個段,舉個栗子,函式func1()會編譯成.text.func1段,雖然段多了,但對連結後代碼大小並沒有影響。

       ⑤ LOCAL_CFLAGS += -fdata-sections
       同上,每個data都有一個單獨的段。

       ⑥ LOCAL_LDFLAGS += -Wl --gc-sections
       -Wl,<option>選項是告訴編譯器,將後面選項<option>傳遞給聯結器,-Wl,--gc-sections的意思是使用聯結器ld連結時刪除不用的段。若使用LOCAL_CFLAGS += -ffunction-sections -fdata-sections,則程式碼和資料均被分割成不同的段,若某個函式或資料未被任何函式呼叫,則ld不會連結未被呼叫的函式,從而減小so檔案體積,達到優化so的目的。

       ⑦ LOCAL_LDFLAGS += -fPIC
       PIC(position independent code)用於編譯位置無關程式碼,生成可用於共享庫的位置獨立程式碼。若不新增-fPIC,則載入.so檔案的程式碼段時,程式碼段引用的資料物件需要重定位,重定位會修改程式碼段內容,這樣就導致沒使用這個.so,程式碼段的程序在核心中就會生成這個檔案的拷貝。

       ⑧ LOCAL_LDFLAGS += -Wall 
       這個的意思是wring all 意思在編譯和連結過程中顯示所有警告資訊。

       ⑨其他
       若需要了解其他編譯標誌,可以檢視GCC Command Options 文件