這節主要介紹的內容是Android NDK開發的核心內容和開發總結(包括不少常見問題的解決方案),本節主要分爲三部分:
* JNI技術和javah命令
* Android NDK Dev Guide
* NDK開發中常見的問題html
NDK開發的核心之一即是JNI,在Oracle官方的JNI相關文檔中重要的是裏面的第3-4部分(數據類型和函數),本文不會介紹這些,若是想快速入手能夠查看這位做者的幾篇關於JNI的文章,講得深刻淺出,另外推薦一篇IBM DeveloperWorks上的文章:JNI 對象在函數調用中的生命週期,講得有點深奧喲。java
javah produces C header files and C source files from a Java class. These files provide the connective glue that allow your Java and C code to interact.android
(1)在External Tools Configurations
中新建Program
c++
(2)Location
設置爲/usr/bin/javah
[你可能不是這個位置,試試${system_path:javah}
]shell
(3)Working Directory
設置爲${project_loc}/bin/classes
[適用於Android項目開發]windows
(4)Arguments
設置爲-jni -verbose -d "${project_loc}${system_property:file.separator}jni" ${java_type_name}
數組
(5)OK,之後只要選中要進行"反編譯"的Java Class,而後運行這個External Tool就能夠了!
注意由於個人Arguments
設置爲導出的頭文件是放在項目的jni目錄中,若是不是Android NDK開發的話,請自行修改輸出路徑,還有Working Directory
設置爲${project_loc}/bin
,不要包含後面的/classes
。若是還有問題的話,推薦看下這位做者的JNI相關配置瀏覽器
在ndk的根目錄下有一個html文件document.html
,這個就是Android NDK Dev Guide,用瀏覽器打開能夠看到裏面介紹了NDK開發中的不少配置問題,不一樣版本的NDK差異仍是蠻大的,並且NDK開發中問題會不少,不像SDK開發那麼簡單,因此,一旦出現了問題,運氣好可以Google解決,RP弱的時候只能啃這些Guide來找答案了。這幾篇文章的簡單介紹能夠查看Android Developer上的解釋。對於這部分的內容,能夠閱讀下這位做者的幾篇NDK Dev Guide的翻譯版本,雖然略有過期,可是看後確定會很受用的,下面我簡單介紹下這裏的幾個內容:架構
這篇文章介紹了NDK的目標和NDK開發的簡易實踐過程,後面的那些文章基本上都是圍繞這個核心內容展開的,很是建議閱讀。須要注意的是,NDK只支持Android 1.5版本以上的設備。oracle
Android.mk文件是用來描述源代碼是如何進行編譯的,ndk-build命令實際上對GNU Make命令的一個封裝,因此,Android.mk文件的寫法就相似Makefile的寫法[關於Make的詳細內容能夠看這本書,[GNU Make的中文手冊],雖然是今年讀的,可是我記得的也很少了,老了老了…]
Android.mk文件能夠生成一個動態連接庫或者一個靜態連接庫,可是隻有動態連接庫是會複製到應用的安裝包中的,靜態庫通常是用來生成其餘的動態連接庫的。你能夠在一個Android.mk文件定義一個或者多個module,不一樣的module可使用相同的source file進行編譯獲得。你不須要列出頭文件,也不須要顯示指明要生成的目標文件之間的依賴關係(這些內容在GNU Make中是很重要的,雖然GNU Make中的隱式規則也能夠作到)。下面以hello-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
是編譯系統提供的一個變量,這個變量指向一個特殊的Makefile文件,它會清除全部除了LOCAL_PATH
以外的其餘的LOCAL_XXX
變量。
③LOCAL_MODULE := hello-jni
:必需要指定LOCAL_MODULE
,它是指這個Android.mk要生成的目標,這個名稱是一個不包含空格的惟一的字符串,編譯系統會自動根據名稱進行必定的修改,例如foo.so
和libfoo.so
獲得的都是libfoo.so
!在Java代碼中進行加載的時候使用的是沒有lib
的module名。
④LOCAL_SRC_FILES := hello-jni.c
:指定C/C++源文件列表,不要包含頭文件。若是須要自定義C++源文件的後綴,能夠配置LOCAL_CPP_EXTENSION
參數。注意寫法,我給個例子,必定要記住每行後面加上一個反斜線符,而且反斜線符後面不能再有任何內容,不然編譯會報錯!
LOCAL_SRC_FILES := hello-jni.c \ foo.c \ boo.cpp
⑤include $(BUILD_SHARED_LIBRARY)
:BUILD_SHARED_LIBRARY
是編譯系統提供的一個Makefile文件,它會根據你前面提供的參數來生成動態連接庫,同理,若是是BUILD_STATIC_LIBRARY
的話,即是生成靜態連接庫。
最佳實踐:通常來講,LOCAL_
做爲前綴的通常定義LOCAL Module的變量,PRIVATE_
或者NDK_
或者APP_
通常定義內部使用的變量,lower-case
小寫字母的名稱通常也是定義內部使用的變量或者函數。若是你要在Android.mk文件定義本身的變量,建議使用MY_
做爲前綴!
MY_SOURCES := foo.c ifneq ($(MY_CONFIG_BAR),) MY_SOURCES += bar.c endif LOCAL_SRC_FILES += $(MY_SOURCES)
Android.mk這篇文章中後面詳細介紹了不少編譯系統內置的變量和函數,以及該文件內能夠設置的變量,此處就再也不贅述了。
Application.mk文件描述的是你的應用須要使用哪些native modules,這個文件不是必須的,小項目能夠不用編寫這個文件。這個文件能夠放在兩個不一樣的位置,最經常使用的是放在jni目錄下,和Android.mk文件放在一塊,也能夠放在$NDK/apps/<myapp>/
目錄下(不推薦使用後者,若是使用的是後者,那麼必需要顯示指定APP_PROJECT_PATH
)
①APP_MODULES
:這個參數在NDK r4以前是必定要指定的,以後即是可選的,默認狀況下,NDK將編譯Android.mk文件中定義的全部的modules。
②APP_CFLAGS
:這個參數用來指定編譯C/C++文件選項參數,例如-frtti -fexceptions
等等,而APP_CPPFLAGS
是專門用來指定編譯C++源文件的選項參數。
③APP_ABI
:這個參數很重要,默認狀況下,ndk-build將生成對應armeabi
CPU架構的庫文件,你能夠指定其餘的CPU架構,或者同時指定多個(自從NDK r7以後,設置爲all
能夠生成全部CPU架構的庫文件)!關於不一樣CPU架構的介紹在CPU Arch ABIs
中介紹了,我不是很懂,此文不細講。若是想要查看某個android設備是什麼CPU架構,能夠上網查設備的資料,或者經過執行adb shell getprop ro.product.cpu.abi
獲得,下面這段摘自OpenCV for Android SDK
armeabi, armv7a-neon, arm7a-neon-android8, mips and x86 stand forplatform targets: * armeabi is for ARM v5 and ARM v6 architectures with Android API 8+, * armv7a-neon is for NEON-optimized ARM v7 with Android API 9+, * arm7a-neon-android8 is for NEON-optimized ARM v7 with Android API 8, * mips is for MIPS architecture with Android API 9+, * x86 is for Intel x86 CPUs with Android API 9+. If using hardware device for testing/debugging, run the following command to learnits CPU architecture: *** adb shell getprop ro.product.cpu.abi *** If you’re using an AVD emulator, go Window > AVD Manager to see thelist of availible devices. Click Edit in the context menu of theselected device. In the window, which then pop-ups, find the CPU field.
④APP_STL
:指定STL,默認狀況下ndk編譯系統使用最精簡的C++運行時庫/system/lib/libstdc++.so
,可是你能夠指定其餘的。詳細的內容能夠查看$NDK/docs/CPLUSPLUS-SUPPORT.html
文件,這個文件可能並無列出在document.html中!
system -> Use the default minimal system C++ runtime library. gabi++_static -> Use the GAbi++ runtime as a static library. gabi++_shared -> Use the GAbi++ runtime as a shared library. stlport_static -> Use the STLport runtime as a static library. stlport_shared -> Use the STLport runtime as a shared library. gnustl_static -> Use the GNU STL as a static library. gnustl_shared -> Use the GNU STL as a shared library.
咱們能夠從下面的表格中看出它們對C++語言特性的支持程度:
C++ C++ Standard Exceptions RTTI Library system no no no gabi++ yes yes no stlport yes yes yes gnustl yes yes yes
從中咱們能夠看出gnustl很不錯,因此通常會配置爲gnustl_static。若是選用的是gnustl的話,通常還須要在C/C++ General
下的Paths and Symbols
中的GNU C
和GNU C++
配置裏添加${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/include
和 ${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/include
這兩項。
另外須要注意的是,若是你指定的是xxx_shared
,想要在運行時加載它,而且其餘的庫是基於xxx_shared
的話,必定記得要先加載xxx_shared
,而後再去加載其餘的庫。
⑤APP_PLATFORM
:指定目標android系統版本,注意,指定的是API level
,通常狀況下,這裏可能會與AndroidManifest.xml
文件中定義的minSdkVersion
衝突而報錯,處理辦法是相似上一節中提到的修改APP_PLATFORM
保證兩個不衝突就好了。
build system會自動加載C庫,Math庫以及C++支持庫,因此你不須要經過LOCAL_LDLIBS
指定加載他們。Android系統下有多個API level
,每一個API level
都對應了一個Android的發佈系統,對應關係以下所示。其中android-6
,android-7
和android-5
是同樣的NDK,也就是說他們提供的是相同的native ABIs。對應API level
的頭文件都放在了$NDK/platforms/android-<level>/arch-arm/usr/include
目錄下,這正是上一節中導入的項目中在C/C++ General
下的Paths and Symbols
中的GNU C
和GNU C++
配置。
Note that the build system automatically links the C library, the Math library and the C++ support library to your native code, there is no need to list them in a LOCAL_LDLIBS line. There are several "API Levels" defined. Each API level corresponds to a given Android system platform release. The following levels are currently supported: android-3 -> Official Android 1.5 system images android-4 -> Official Android 1.6 system images android-5 -> Official Android 2.0 system images android-6 -> Official Android 2.0.1 system images android-7 -> Official Android 2.1 system images android-8 -> Official Android 2.2 system images android-9 -> Official Android 2.3 system images android-14 -> Official Android 4.0 system images Note that android-6 and android-7 are the same as android-5 for the NDK, i.e. they provide exactly the same native ABIs! IMPORTANT: The headers corresponding to a given API level are now located under $NDK/platforms/android-<level>/arch-arm/usr/include
介紹幾個比較重要的庫:
(1)C庫(libc):不須要指定 –lpthread –lrt,也就是說它會自動連接
(2)C++庫(lstdc++):不須要指定 –lstdc++
(3)Math庫(libm):不須要指定 –lm
(4)動態連接器庫(libdl):不須要指定 –ldl
(5)Android log(liblog):須要指定 –llog
(6)Jnigraphics庫(libjnigraphics):這個C語言庫提供了對Java中Bitmap的操做,須要指定 –ljnigraphics,這個庫是android-8
新增長的內容,典型的使用方式是:
Briefly, typical usage should look like: 1/ Use AndroidBitmap_getInfo() to retrieve information about a given bitmap handle from JNI (e.g. its width/height/pixel format) 2/ Use AndroidBitmap_lockPixels() to lock the pixel buffer and retrieve a pointer to it. This ensures the pixels will not move until AndroidBitmap_unlockPixels() is called. 3/ Modify the pixel buffer, according to its pixel format, width, stride, etc.., in native code. 4/ Call AndroidBitmap_unlockPixels() to unlock the buffer.
(7)The Android native application APIs:android-9
新增長的內容,這些API使得你能夠徹底使用native code編寫android app,可是通常狀況下仍是須要經過jni的,相關API以下:
The following headers correspond to these new native APIs (see comments inside them for more details): <android/native_activity.h> Activity lifecycle management (and general entry point) <android/looper.h> <android/input.h> <android/keycodes.h> <android/sensor.h> To Listen to input events and sensors directly from native code. <android/rect.h> <android/window.h> <android/native_window.h> <android/native_window_jni.h> Window management, including the ability to lock/unlock the pixel buffer to draw directly into it. <android/configuration.h> <android/asset_manager.h> <android/storage_manager.h> <android/obb.h> Direct (read-only) access to assets embedded in your .apk. or the Opaque Binary Blob (OBB) files, a new feature of Android X.X that allows one to distribute large amount of application data outside of the .apk (useful for game assets, for example). All the corresponding functions are provided by the "libandroid.so" library version that comes with API level 9. To use it, use the following: LOCAL_LDLIBS += -landroid
使用ndk-build
命令(ndk r4以後引入的)其實是GNU Make的封裝,它等價於make -f $NDK/build/core/build-local.mk [參數]
命令。系統必需要安裝GNU Make 3.81以上版本,不然編譯將報錯!若是你安裝了GNU Make 3.81,可是默認的make命令沒有啓動,那麼能夠在執行ndk-build
以前定義GNUMAKE這個變量,例如GNUMAKE=/usr/local/bin/gmake ndk-build
。
注意 在Windows下進行NDK開發的話,通常使用的是Cygwin自帶的Make工具,可是默認是使用NDK的awk工具,因此可能會報一個錯誤Android NDK: Host 'awk' tool is outdated. Please define HOST_AWK to point to Gawk or Nawk !
解決方案就是刪除NDK自帶的awk工具(參考網址),這也就是第一節中使用ndk-build -v
命令獲得的GNU Make信息輸出不一樣了,嘿嘿,我這伏筆埋的夠深吧!其實,也可使用下面的方式直接覆蓋系統的環境變量
NDK_HOST_AWK=<path-to-awk> NDK_HOST_ECHO=<path-to-echo> NDK_HOST_CMP=<path-to-cmp>
若是仍是不行的話,參見StackOverflow上的解答
在Windows先開發還有一個須要注意的是,若是是使用Cygwin對native code進行編譯,那麼須要在使用ndk-build
以前調用NDK_USE_CYGPATH=1
!(不過不用每次都使用)
下面是ndk-build命令的可用參數,比較經常使用的是 ndk-build NDK_DEBUG=1
或者 ndk-build V=1
ndk-build --> rebuild required machine code. ndk-build clean --> clean all generated binaries. ndk-build NDK_DEBUG=1 --> generate debuggable native code. ndk-build V=1 --> launch build, displaying build commands. ndk-build -B --> force a complete rebuild. ndk-build -B V=1 --> force a complete rebuild and display build commands. ndk-build NDK_LOG=1 --> display internal NDK log messages (used for debugging the NDK itself). ndk-build NDK_DEBUG=1 --> force a debuggable build (see below) ndk-build NDK_DEBUG=0 --> force a release build (see below) ndk-build NDK_HOST_32BIT=1 --> Always use toolchain in 32-bit (see below) ndk-build NDK_APPLICATION_MK=<file> --> rebuild, using a specific Application.mk pointed to by the NDK_APPLICATION_MK command-line variable. ndk-build -C <project> --> build the native code for the project path located at <project>. Useful if you don't want to 'cd' to it in your terminal.
[6]NDK GDB,Import Module,Prebuilts,Standalone Toolchains以及和CPU相關的三個內容由於我沒有涉及過,本身也不是很瞭解,因此此處暫時擱置了,之後若是用到之後補充。關於NDK調試環境的搭建能夠參見這位做者的實踐博文
在Windows下修改hosts文件:C:\Windows\System32\drivers\etc
增長以下一行配置:74.125.237.1 dl-ssl.google.com
Fatal signal 11 (SIGSEGV) at 0x00000004 (code=1), thread 23487 (mple)
錯誤緣由是由於訪問了非法訪問的內存地址,具體的緣由多是訪問了null對象或者數組,頗有多是Java層傳給Native層的對象是null,致使Native層訪問了非法訪問的地址。參考網址1 參考網址2
默認狀況下avd對應的目錄是隻讀的,去掉只讀就行了。參考網址
add Native Support
報錯使用add Native Support
時必定要記住項目不能有jni目錄!若是有的話,那就只能先刪除(或者備份重要內容),而後再執行add Native Support
。
使用自定義的將jstring轉換成char*的函數,內容以下:
c++ static char* jstringToString(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); //"gbk");// jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*) malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = '\0'; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn; }