Android NDK 編譯選項設置

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

V2EX格式編輯起來比較累,大家可以查看原文鏈接 http://crash.163.com/#news/!newsId=24

一、 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)調CLEARVARSBUILDSHAREDLIBRARYTARGETARCHNDKAndroid.mkAndroid.mkModuledescriptionLOCALPATHLOCALMODULELOCALSRCFILESLOCALLOCALPATHinclude (CLEAR_VARS)和 include $(BUILD_XXX)之間。
其他 Android.mk 配置可以查看 Android.mk 官方文檔。

2 、基礎

在 Android.mk 中包括一些很基礎的變量,下面的栗子包括了基礎的變量,本人將詳細說明。
LOCAL_PATH := (callmydir)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 (CLEARVARS)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 (CLEARVARS)LOCALMODULE:=testLOCALSRCFILES:=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,選項是告訴編譯器,將後面選項傳遞給連接器,-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 文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章