眾所周知,Android作為目前主流的移動終端領域的開發平台,其主要的開發語言就是JAVA。Android借助於JAVA高效、靈活的開發模式,迅速占領了移動互聯網開發的半壁江山。基於JDK和Android實現的各種JAVA框架,開發人員可以快速實現各種功能的APP,而且APP的安裝和升級維護都實現的方便。
對於,AndroidJAVA的開發模式大家已經很熟悉了,今天要說的是基於Android 平台如何開發原生的C/C++應用程序程序。Google對於AndroidC/C++開發提供了NDK(Native Development Kit),基於NDK我們可以編譯出能夠在Android平台上運行了動態庫/靜態庫/可執行程序。對於動態庫/靜態庫,JAVA程序可以通過JNI(Java Native Interface)機制實現與其通信。
說到這裡,可能大家覺得開發Android C/C++程序還需要花時間學習NDK的使用方式,未免有些麻煩。其實,基於NDK可以生成傳統意義上的交叉開發工具鏈,對於經常開發嵌入式Linux應用程序的開發人員來說,對於交叉工具鏈的使用再熟悉不過了,所以對於不願意使用NDK開發方式的程序員來說,使用交叉工具鏈則簡單、高效的多,下面分別介紹這兩種開發方式。
下面是官方對於NDK的簡要介紹:NDK是一套可以允許APP調用像C或者C++編寫的原生代碼的工具集。對於某些類型的APP,
NDK可以實現現有代碼庫的復用,但對於大多數的APP來說不需要NDK提供的這項服務。
官方文檔同時說明,NDK對於大多數的APPs並沒有太多的好處。並提醒,作為一名開發者,需要平衡使用NDK的利與弊。需要注意的是,在Android平台上使用原生的代碼,一般不會帶來可觀的性能提升,但它常常增加APP的復雜度。所以,除非你的APP必須使用NDK,其他情況下,不要僅僅為了使用C/C++編程而使用NDK。
典型的使用NDK的場景是CPU密集型的應用,例如游戲引擎、信號處理、物理環境模擬等。當你使用NDK之前,請確定Android框架提供的APIs是否滿足你的需求。
好了,上面就是官方對於NDK的簡單介紹,具體使用場景大家可以參考上面提到的注意事項。下面就對NDK的具體使用方式做一下介紹。
首先,介紹介紹一下主機開發環境:
操作系統:Ubuntu14.04 TLS 32bit
內存:4G
NDK的官方下載地址:http://wear.techbrood.com/tools/sdk/ndk/; 由於系統為32bit的所以只能下載32bit的NDK,下載的版本為:Android NDK r10b。
NDK的安裝十分的簡單,將下載來的NDK,解壓就可使用,例如:
$tar jxvf android-ndk32-r10b-linux-x86.tar.bz2 -C /to/NDK/path
這樣NDK就安裝好了,你可以按照自己的習慣重新給NDK的開發目錄命名。
使用NDK之前需要將NDK的安裝目錄加入到PATH環境變量中,具體的PATH變量可以配置到~/.bashrc文件中,配置完成後,使用source命令使之生效。
$source ~/.bashrc
NDK依賴於Android.mk完成具體的編譯過程,Android.mk類似於GNU Makefile文件,下面的舉一個Android.mk簡單的例子。
LOCAL_PATH := $(call my-dir)_x000D_ include $(CLEAR_VARS)_x000D_ LOCAL_MODULE := hello-jni_x000D_ LOCAL_SRC_FILES := hello-jni.c_x000D_ include $(BUILD_SHARED_LIBRARY)
下面是對於上面Android.mk的解析:
① LOCAL_PATH:=$(call my-dir):每個Android.mk的開始位置必須定義LOCAL_PATH。其用於定義源文件的根目錄。在本例子中,函數’my-dir’為構建系統提供的內建函數,其用於返回Android.mk所在當前目錄。
② Include $(CLEAR_VARS):CLEAR_VARS為構建系統提供的變量,其指向一個GNU Makefile,用來重置除LOCAL_PATH之外的所有以LOCAL開頭的變量(例如,LOCAL_MODULE、LOCAL_SRC_FILES等)。這是必須的,因為在GNU Makefile執行系統中所有的構建控制文件的變量都是全局的,為了不相互干擾,所以在Android.mk中必須調用CLAER_VARS重置以LOCAL開頭的變量。
③ LOCAL_MODULE:=hello-jni:LOCAL_MODULE變量用來定義Android.mk所描述的目標模塊名稱。這個名字必須為唯一的,並且不能帶有空格。構建系統會自動的為其添加適當的前綴和後綴。例如,動態庫的模塊名‘foo’為會被命名為’libfoo.so’;需要注意的是如果模塊的名稱為libfoo,構建系統不會在為其添加‘lib’前綴,同樣最後生成’libfoo.so’。
④ LOCAL_SRC_FILES := hello-jni.c:LOCAL_SRC_FILES變量包含所有需要被編譯進模塊中的C/C++源文件。需要注意的是,你不需要對應的頭文件定義出來,構建系統會自動的為你分析依賴關系。
⑤ Include $(BUILD_SHARED_LIBRARY):BUILD_SHARED
_LIBRARY變量為構建系統提供的內建GNU Makefile腳本,其會收集距其最近的‘include $(CLEAR_VARS)之後的所有LOCAL_X變量的信息,從而決定如何構建最終的目標文件。與其相對應的是BUILD_STATIC_LIBRARY,其用來生成靜態庫。
上面為一個最為簡單的Android.mk的分析,下面具體分析一下Android.mk的具體細節。SOURCES
以下是在 Android.mk中依賴或定義的變量列表,可以定義其他變量為自己使用,但是NDK編譯系統保留下列變量名:
a. 以LOCAL_開頭的名字(例如LOCAL_MODULE)
b. 以PRIVATE_, NDK_ 或 APP_開頭的名字(內部使用)
c. 小寫名字(內部使用,例如‘my-dir’)
如果為了方便在 Android.mk 中定義自己的變量,建議使用前綴MY_,例如下面的例子:
MY_SOURCES:=foo.c
ifneq($(MY_CONFIG_BAR),)
MY_SOURCES+=bar.c
endif
LOCAL_SRC_FILES += $(MY_SOURCES)
注意:‘:=’是賦值的意思;'+='是追加的意思;‘$’表示引用某變量的值。
這些 GNU Make變量在你的 Android.mk 文件解析之前,就由編譯系統定義好了。注意在某些情況下,NDK可能分析 Android.mk 幾次,每一次某些變量的定義會有不同。
(1)CLEAR_VARS: 指向一個編譯腳本,幾乎所有未定義的 LOCAL_XXX 變量都在"Module-description"節中列出。必須在開始一個新模塊之前包含這個腳本:include$(CLEAR_VARS),用於重置除LOCAL_PATH變量外的,所有LOCAL_XXX系列變量。
(2)BUILD_SHARED_LIBRARY: 指向編譯腳本,根據所有的在 LOCAL_XXX 變量把列出的源代碼文件編譯成一個共享庫。
注意,必須至少在包含這個文件之前定義 LOCAL_MODULE 和 LOCAL_SRC_FILES.
(3)BUILD_STATIC_LIBRARY: 一個 BUILD_SHARED_LIBRARY 變量用於編譯一個靜態庫。靜態庫不會復制到的APK包中,但是可以用於編譯共享庫。
示例:include$(BUILD_STATIC_LIBRARY)
注意,這將會生成一個名為 lib$(LOCAL_MODULE).a 的文件
(4)TARGET_ARCH:目標 CPU平台的名字
(5)TARGET_PLATFORM: Android.mk 解析的時候,目標 Android 平台的名字.詳情可考/development/ndk/docs/stable- apis.txt.
android-3 -> Official Android 1.5 system images
android-4 -> Official Android 1.6 system images
android-5 -> Official Android 2.0 system images
... ...
(6)TARGET_ARCH_ABI: 暫時只支持兩個 value,armeabi 和 armeabi-v7a。。
(7)TARGET_ABI: 目標平台和 ABI 的組合
(8)BUILD_EXECUTABLE:編譯成為一個可執行文件。
GNU Make函數宏,必須通過使用'$(call )'來調用,返回值是文本化的信息。
(1)my-dir:返回當前 Android.mk 所在的目錄的路徑,相當於於 NDK 編譯系統的頂層。這是有用的,在 Android.mk 文件的開頭如此定義:
LOCAL_PATH:=$(callmy-dir)
(2)all-subdir-makefiles: 返回一個位於當前'my-dir'路徑的子目錄中的所有Android.mk的列表。 例如,某一子項目的目錄層次如下:
src/foo/Android.m
src/foo/lib1/Android.mk
src/foo/lib2/Android.mk
如果src/foo/Android.mk包含:include $(call all-subdir-makefiles) 那麼它就會自動包含 src/foo/lib1/Android.mk 和 src/foo/lib2/Android.mk。
這這項功能用於向編譯系統提供深層次嵌套的代碼目錄層次項功能用於向編譯系統提供深層次嵌套的代碼目錄層次。
注意,在默認情況下,NDK 將會只搜索在 src/*/Android.mk 中的文件。
(3)this-makefile: 返回當前Makefile 的路徑(即這個函數調用的地方)。
(4)parent-makefile: 返回調用樹中父 Makefile 路徑。即包含當前Makefile的Makefile 路徑。
(5)grand-parent-makefile:返回調用樹中父Makefile的父Makefile的路徑
下面的變量用於向編譯系統描述你的模塊。應該定義在'include $(CLEAR_VARS)'和'include $(BUILD_XXXXX)'之間。$(CLEAR_VARS)是一個腳本,清除所有這些變量。
(1)LOCAL_PATH: 這個變量用於給出當前文件的路徑。
必須在 Android.mk 的開頭定義,可以這樣使用:LOCAL_PATH:=$(call my-dir) ,如當前目錄下有個文件夾名稱 src,則可以這樣寫 $(call src),那麼就會得到 src 目錄的完整路徑 。這個變量不會被$(CLEAR_VARS)清除,因此每個 Android.mk 只需要定義一次(即使在一個文件中定義了幾個模塊的情況下)。
(2)LOCAL_MODULE: 這是模塊的名字,它必須是唯一的,而且不能包含空格。必須在包含任一的$(BUILD_XXXX)腳本之前定義它。模塊的名字決定了生成文件的名字。
(3)LOCAL_SRC_FILES: 這是要編譯的源代碼文件列表。
只要列出要傳遞給編譯器的文件,因為編譯系統自動計算依賴。注意源代碼文件名稱都是相對於 LOCAL_PATH的,你可以使用路徑部分,例如:
LOCAL_SRC_FILES:=foo.c toto/bar.c\
Hello.c 文件之間可以用空格或Tab鍵進行分割,換行請用"\"
如果是追加源代碼文件的話,請用LOCAL_SRC_FILES += 。
注意:可以LOCAL_SRC_FILES := $(call all-subdir-java-files)這種形式來包含local_path目錄下的所有java文件。
(4)LOCAL_C_INCLUDES: 可選變量,表示頭文件的搜索路徑。
默認的頭文件的搜索路徑是LOCAL_PATH目錄。
(5)LOCAL_STATIC_LIBRARIES: 表示該模塊需要使用哪些靜態庫,以便在編譯時進行鏈接。
(6)LOCAL_SHARED_LIBRARIES: 表示模塊在運行時要依賴的共享庫(動態庫),在鏈接時就需要,以便在生成文件時嵌入其相應的信息。
(7)LOCAL_LDLIBS: 編譯模塊時要使用的附加的鏈接器選項。這對於使用‘-l’前綴傳遞指定庫的名字是有用的。
例如,LOCAL_LDLIBS := -lz表示告訴鏈接器生成的模塊要在加載時刻鏈接到/system/lib/libz.so 。可查看 docs/STABLE-APIS.TXT 獲取使用 NDK發行版能鏈接到的開放的系統庫列表。
(8)LOCAL_CFLAGS:用於指定C/C++編譯器所使用的編譯選項。
注意:不要試圖通過LOCAL_CFLAGS修改Android.mk的優化/調試等級,Application.mk中定義的信息會自動控制,並讓NDK產生有意義的調試信息。
可以通過如下設置完成額外的頭文件的指定:
LOCAL_CFLAGS += -I
但使用最好使用LOCAL_C_INCLUDES,因為ndk-gdb同樣會使用這些路徑。
更多的Android.mk信息請參看NDKdocs中的Android.mk
Application.mk目的是描述在你的應用程序中所需要的模塊(即靜態庫或動態庫)。
Application.mk文件通常被放置在 $PROJECT/jni/Application.mk下,$PROJECT指的是您的項目。
要將C\C++代碼編譯為SO文件,光有Android.mk文件還不行,還需要一個Application.mk文件。
下面具體描述Application.mk的語法使用。
1. APP_PROJECT_PATH :這個變量是強制性的,並且會給出應用程序工程的根目錄的一個絕對路徑。這是用來復制或者安裝一個沒有任何版本限制的JNI庫,從而給APK生成工具一個詳細的路徑。
2. APP_MODULES :這個變量是可選的,如果沒有定義,這個模塊名字被定義在Android.mk文件中的 LOCAL_MODULE 中。 NDK將由在Android.mk中聲明的默認的模塊編譯,並且包含所有的子文件(makefile文), NDK會自動計算模塊的依賴。如果APP_MODULES定義了,它必須是一個空格分隔的模塊列表( 注意:NDK在R4開始改變了這個變量的行為,在此之前: 在Application.mk中,該變量是強制的必須明確列出所有需要的模塊)
3. APP_OPTIM:這個變量是可選的,用來定義“release”或"debug"。在編譯您的應用程序模塊的時候,可以用來改變優先級。 “release”模式是默認的,並且會生成高度優化的二進制代碼。"debug"模式生成的是未優化的二進制代碼,但可以檢測出很多的BUG,可以用於調試。注意:如果你的應用程序是可調試的(即,如果你的清單文件在它的標簽中把android:debuggable屬性設為true),
默認將是debug而非release。把APP_OPTIM設置為release可以覆寫它。注意:可以調試release和debug版二進制,但release版構建傾向於在調試會話中提供較少信息:一些變量被優化並且不能被檢測,碼重新排序可能致使代碼步進變得困難,堆棧跟蹤可能不可靠,等等。
4. APP_CFLAGS:一個C編譯器開關集合,在編譯任意模塊的任意C或C++源代碼時傳遞。它可以用於改變一個給定的應用程序需要依賴的模塊的構建,而不是修改它自身的Android.mk文件
5. APP_BUILD_SCRIPT:默認,NDK構建系統將在 $(APP_PROJECT_PATH)/jni 下尋找一個名為 Android.mk 的文件。即,對於這個文件$(APP_PROJECT_PATH)/jni/Android.mk如果你想重載這個行為,你可以定義APP_BUILD_SCRIPT指向一個不同的構建腳本。一個非絕對路徑將總是被解析為相對於NDK頂級目錄的路徑。
6. APP_ABI : 默認情況下,NDK的編譯系統根據 "armeabi" ABI生成機器代碼。可以使用APP_ABI 來選擇一個不同的ABI。比如:為了在ARMv7的設備上支持硬件FPU指令。可以使用 APP_ABI := armeabi-v7a或者為了支持IA-32指令集,可以使用 APP_ABI := x86或者為了同時支持這三種,可以使用 APP_ABI := armeabi armeabi-v7a x86
7. APP_STL :默認,NDK構建系統提供由Android系統給出的最小C++運行時庫(/system/lib/libstdc++.so)的C++頭文件。 然而,NDK帶有另一個C++實現,你可以在你自己的應用程序中使用或鏈接它。定義APP_STL以選擇它們其中的一個:
APP_STL := stlport_static --> static STLport library
APP_STL := stlport_shared --> shared STLport library
APP_STL := system --> default C++ runtime library
更多的Application.mk信息請參看NDKdocs中的Application.mk
上面將NDK的安裝、配置以及Android.mk和Application.mk的編寫格式都進行了一些介紹,下面舉幾個簡單的基於NDK的程序編寫例子。本節介紹C/C++動態庫和靜態庫的編寫方式。下面是程序的源代碼,很簡單只打印“Hello, NDK!”。
首先,建立NDK的工作目錄,例如:
$mkdir -p ~/android_ndk ~/android_ndk/jni
打開~/.bashrc文件,添加NDK_PROJECT_PATH環境變量,如下:
export NDK_PROJECT_PATH=~/android_ndk
並執行sourcr ~/.bashrc,是剛才配置的環境變量生效。
進入android_ndk目錄,創建hello_ndk.c文件,代碼如下:
#include
int hello_ndk(void)
{
printf("Hello, NDK\n");
return 0;
}
進入~/andorid_ndk目錄,編寫Android.mk,配置如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE = hello
LOCAL_SRC_FILES = hello.c
include $(BUILD_SHARED_LIBRARY)#編譯libhello.so動態庫
#include $(BUILD_STATIC_LIBRARY)#編譯靜態庫libhello.a
進入~/android_ndk/jni目錄,編寫Application.mk,配置如下:
APP_BUILD_SCRIPT=~android_c_cpp/Android.mk
APP_PROJECT_PATH=~/android_c_cpp/
APP_OPTIM=release
APP_ABI=armeabi
進入~/android_ndk目錄,執行ndk-build,創建libhello.so動態庫,如下:
[armeabi] Compile thumb : hello <= hello.c
[armeabi] SharedLibrary : libhello.so
[armeabi] Install : libhello.so => libs/armeabi/libhello.so
NDK同樣可以編譯能夠在Android系統運行的可執行程序,將該程序通過adb置於android系統中,就可以直接運行它。生成可執行的程序的Androd.mk,如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE = hell_ndk
LOCAL_CFLAGS=-pie -fPIE#生成位置無關的可執行程序
LOCAL_SRC_FILES = main.c
include $(BUILD_EXECUTABLE)#生成可執行程序
[armeabi] Compile thumb : hello <= hello.c
[armeabi] Compile thumb : hello <= main.c
[armeabi] Executable : hello
[armeabi] Install : hello => libs/armeabi/hello
有時,我們在編譯可執行程序時,需要鏈接外部的動態庫,那Android.mk該如何編寫呢?根據Android.mk一節,我們需要用到兩個NDK變量:LOCAL_SHARED_LIBRARIES和LOCAL_LDLIBS。
例如,我們已經生成了libhello.so,如果另一個文件main.c需要調用hello_ndk函數,我們就需要調用libhello.so庫,main.c如下:
#include "hello.h"
int main(void)
{
hello_ndk();
return 0;
}
Android.mk如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE = hello_ndk
LOCAL_SHARED_LIBRARIES=libhello
LOCAL_LDLIBS=-L./ -lhello #libhello.so動態庫,注意必須添加-L制定動態庫的位置
LOCAL_CFLAGS=-pie -fPIE
LOCAL_SRC_FILES = main.c
include $(BUILD_EXECUTABLE)
如果開發者已經習慣了使用交叉工具完成應用程序的編譯工作,NDK提供了了Standalone Toolchian方式,我們可以直接通過NDK生成交叉工具鏈,然後直接修改Makfile中的CC變量就可以直接生成可以在Android系統上運行的應用程序。
首先,進入NDK的安裝目錄,進入build/tools目錄,然後可以看make-standalone-toolchain.sh即為StandaloneToolchain的生成工具。
可以運行如下命令,查看其使用方法:
$./make-standalone-toolchain.sh --help
--help Print this help.
--verbose Enable verbose mode.
--toolchain=
--llvm-version=
--stl=
--arch=name> 指定目標平台例如:arm、x86、mips
--abis=
--ndk-dir=
--system=
--package-dir=
--install-dir=
--platform=
下面為一個具體配置方式:
$./make-standalone-toolchain.sh --toolchain=arm-linux-androideabi-4.8 --ndk-dir=/home/lhl/android_ndk/android-ndk-r10b/ --package-dir=/tmp/ndk-4.8 --platform=android-19
完成後會在/tmp/ndk-4.8目錄下生成arm-linux-androideabi-4.8交叉工具鏈的壓縮包,arm-linux-androideabi-4.8.tar.bz2。將其到任意目錄配置PATH環境變量後就可以想普通的交叉工具鏈一樣使用了。
libc (C library) headers
libm (math library) headers
JNinterface headers
libz (Zlib compression) headers
liblog (Android logging) header
OpenGL ES 1.1 and OpenGL ES 2.0 (3D graphics libraries) headers
libjnigraphics (Pixel buffer access) header (for Android 2.2 and above).
A Minimal set of headers for C++ support
OpenSL ES native audio libraries
Android native application APIS
NOTE:對於posixpthread的使用我們沒有必要在LOCAL_LIBS中指定-lpthread,libc中已經包含了pthread的實現。同樣對於rt(real-time extentions (-rt),同樣已經包含進了libc庫。