Android NDK和OpenCV整合開發 (2) Android NDK

Android NDK 和 OpenCV 整合開發 (2) Android NDK

這節主要介紹的內容是Android NDK開發的核心內容和開發總結(包括不少常見問題的解決方案),本節主要分爲三部分:
* JNI技術和javah命令
* Android NDK Dev Guide
* NDK開發中常見的問題html

1.不得不說的JNI和javah命令

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

  • 在Eclipse中配置萬能的javah工具的方法

(1)在External Tools Configurations中新建Programc++

(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相關配置瀏覽器

2.那些年的Android NDK Dev Guide

在ndk的根目錄下有一個html文件document.html,這個就是Android NDK Dev Guide,用瀏覽器打開能夠看到裏面介紹了NDK開發中的不少配置問題,不一樣版本的NDK差異仍是蠻大的,並且NDK開發中問題會不少,不像SDK開發那麼簡單,因此,一旦出現了問題,運氣好可以Google解決,RP弱的時候只能啃這些Guide來找答案了。這幾篇文章的簡單介紹能夠查看Android Developer上的解釋。對於這部分的內容,能夠閱讀下這位做者的幾篇NDK Dev Guide的翻譯版本,雖然略有過期,可是看後確定會很受用的,下面我簡單介紹下這裏的幾個內容:架構

  • [1]Android NDK Overview

這篇文章介紹了NDK的目標和NDK開發的簡易實踐過程,後面的那些文章基本上都是圍繞這個核心內容展開的,很是建議閱讀。須要注意的是,NDK只支持Android 1.5版本以上的設備。oracle

  • [2]Android.mk文件

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_PATHmy-dir是編譯系統提供的一個宏函數,這個宏函數會返回當前Android.mk文件所在的目錄

include $(CLEAR_VARS)CLEAR_VARS是編譯系統提供的一個變量,這個變量指向一個特殊的Makefile文件,它會清除全部除了LOCAL_PATH以外的其餘的LOCAL_XXX變量。

LOCAL_MODULE := hello-jni:必需要指定LOCAL_MODULE,它是指這個Android.mk要生成的目標,這個名稱是一個不包含空格的惟一的字符串,編譯系統會自動根據名稱進行必定的修改,例如foo.solibfoo.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這篇文章中後面詳細介紹了不少編譯系統內置的變量和函數,以及該文件內能夠設置的變量,此處就再也不贅述了。

  • [3]Application.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將生成對應armeabiCPU架構的庫文件,你能夠指定其餘的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 CGNU 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保證兩個不衝突就好了。

  • [4]Stable-APIS

build system會自動加載C庫,Math庫以及C++支持庫,因此你不須要經過LOCAL_LDLIBS指定加載他們。Android系統下有多個API level,每一個API level都對應了一個Android的發佈系統,對應關係以下所示。其中android-6android-7android-5是同樣的NDK,也就是說他們提供的是相同的native ABIs。對應API level的頭文件都放在了$NDK/platforms/android-<level>/arch-arm/usr/include目錄下,這正是上一節中導入的項目中在C/C++ General下的Paths and Symbols中的GNU CGNU 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
  • [5]NDK Build

使用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調試環境的搭建能夠參見這位做者的實踐博文

  • [7]Tips and Tricks 建議和技巧

那些曾經的頭疼的問題

  • [1]使用Android SDK Manager下載SDK時失敗或者很慢

在Windows下修改hosts文件:C:\Windows\System32\drivers\etc
增長以下一行配置:74.125.237.1 dl-ssl.google.com

  • [2]Fatal signal 11 (SIGSEGV) at 0x00000004 (code=1), thread 23487 (mple)

錯誤緣由是由於訪問了非法訪問的內存地址,具體的緣由多是訪問了null對象或者數組,頗有多是Java層傳給Native層的對象是null,致使Native層訪問了非法訪問的地址。參考網址1 參考網址2

  • [3]使用ADB命令向AVD中複製文件或文件夾時報錯

默認狀況下avd對應的目錄是隻讀的,去掉只讀就行了。參考網址

  • [4]對android項目執行add Native Support報錯

使用add Native Support時必定要記住項目不能有jni目錄!若是有的話,那就只能先刪除(或者備份重要內容),而後再執行add Native Support

  • [5]將String傳遞到Native層解析出現了亂碼!

使用自定義的將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; }

  • [6]To be continued
相關文章
相關標籤/搜索