Android端通過JNI調(diào)用OpenCV庫詳解

1 Android NDK 應(yīng)用場景

當(dāng)我們已有在其他平臺(tái)上編寫的C或C++代碼時(shí),我們可以使用NDK(Native Development Kit)在Android平臺(tái)中生成相應(yīng)的.so庫并調(diào)用。Android程序運(yùn)行在Dalvik虛擬機(jī)中,NDK允許用戶使用類似C/C++之類的原生代碼語言執(zhí)行部分程序。除此以外,NDK還提供了對代碼的保護(hù)作用,這是由于apk的java層代碼很容易被反編譯,而C/C++庫反編難度較大。Android NDK APP的主要構(gòu)成如圖所示。

Android NDK對于Android SDK只是個(gè)組件,它可以幫我們生成的JNI兼容的共享庫可以在大于Android1.5平臺(tái)的ARM CPU上運(yùn)行,將生成的共享庫拷貝到合適的程序工程路徑的位置上,以保證它們自動(dòng)的添加到你的apk包中(并且簽名的)。而且,Android NDK還提供了

  • 一組交叉編譯鏈(編譯器、鏈接器等)來生成可以在Linux,OS X和Windows運(yùn)行的二進(jìn)制文件;

  • 一組與由Android平臺(tái)提供的穩(wěn)定的本地API列表的頭文件,它們在docs/STABLE-APIS.html中有說明;

  • 一個(gè)編譯系統(tǒng)(build system)可以允許開發(fā)者寫一個(gè)非常短的編譯文件(build files)去描述哪個(gè)源代碼需要編譯,并且怎樣編譯。編譯系統(tǒng)可以解決所有的toolchain/platform/CPU/ABI細(xì)節(jié)的問題。并且,較晚的NDK版本中還添加了更多的可以不用改變開發(fā)者的編譯文件的情況下的toolchains、platforms、系統(tǒng)接口。
    通過以上的敘述,我們知道Android NDK解決了核心模塊使用托管語言開發(fā)執(zhí)行效率低下的問題;允許程序開發(fā)人員直接使用C/C++源代碼,極大的提高了Android應(yīng)用程序開發(fā)的靈活性。
    但同時(shí)Android NDK也存在著一些不足。
    NDK并不是一個(gè)可以編寫通用的源代碼并且可以在Android設(shè)備上運(yùn)行的方法,你的應(yīng)用程序還是需要使用JAVA程序,適當(dāng)?shù)奶幚硐到y(tǒng)事件來避免“應(yīng)用程序沒有反應(yīng)”的對話框或者處理Android應(yīng)用程序的生命周期。注意:可以適當(dāng)?shù)脑谠创a中寫一個(gè)復(fù)雜的應(yīng)用程序,用于啟動(dòng)/停止一個(gè)小型的“應(yīng)用程序包”。
    NDK在Android平臺(tái)僅僅提供了有限的本地API和庫文件的支持的系統(tǒng)頭文件,然而一個(gè)標(biāo)準(zhǔn)的Android系統(tǒng)鏡像包括許多本地共享庫,這些都應(yīng)該被考慮在更新和發(fā)行版本的可以徹底改變的實(shí)現(xiàn)細(xì)節(jié)。如果Android系統(tǒng)庫沒有明確的被NDK明確的支持,然后應(yīng)用程序不應(yīng)該依賴于它提供的,或者打破了將來在各種設(shè)備上的無線系統(tǒng)更新,選定的系統(tǒng)庫將逐漸被添加到穩(wěn)定的NDK API中。

2 Android NDK應(yīng)用的開發(fā)環(huán)境搭建

2.1 Android Eclipse環(huán)境搭建

我的的開發(fā)環(huán)境基于Eclipse。首先,我們需要到Android官網(wǎng)下載Android的開發(fā)工具ADT(Android Development Tool的縮寫),該工具集成了最新的ADT和NDK插件以及Eclipse,該環(huán)境滿足傳統(tǒng)Android應(yīng)用(Android SDK APP)開發(fā)環(huán)境。為了讓我們的開發(fā)可以編譯C/C++代碼,我們需要為其安裝CDT插件,安裝完畢后打開Help->About Eclipse 如圖所示。

2.2 Android NDK 環(huán)境搭建

首先從Android官網(wǎng)上下載NDK,我們選擇的版本是android-ndk-r10e。下載完成后,將NDK安裝至任意目錄下。
打開Eclipse并創(chuàng)建一個(gè)Android Application Project,我們將其命名為Visodo。完成后其項(xiàng)目結(jié)構(gòu)如圖3所示。選中該項(xiàng)目,右擊進(jìn)入選擇”Properties”,界面如圖4所示。這是典型Android SDK APP的配置目錄,我們可以發(fā)現(xiàn)沒有任何和C/C++相關(guān)的目錄。
我們需要將Android項(xiàng)目轉(zhuǎn)換為C\C++項(xiàng)目(使其具備C++屬性),右擊New -> Other -> C/C++ -> Convert to a C/C++ Project。,此時(shí)Properties界面如圖5所示,從圖上可見,已有了C/C++的相關(guān)屬性。因而,我們也可以開展對NDK相關(guān)屬性的配置工作了。


<center>圖3</center>


<center>圖 4</center>


<center>圖 5</center>
配置NDK編譯路徑,Project->Properties-> C/C++Build,如圖6,取消Use default command的勾選,其中Build-Command中ANDROID_NDK為環(huán)境變量中配置的Android-NDK路徑;Build-Directory為當(dāng)前工程目錄。


<center>圖 6</center>
進(jìn)入Project->Properties-> C/C++Build->Environment,將NDK安裝路徑配置在NDKROOT中,如H:\BaiduYunDownload\android-ndk-r10e,見圖7。


<center>圖 7</center>
進(jìn)入Project->Properties-> C/C++General->Paths and Symbols,進(jìn)行NDK相關(guān)配置,如圖8 。至此,Eclipse自動(dòng)編譯NDK的環(huán)境配置完成,我們可以在該項(xiàng)目中編寫或使用已有C/C++代碼。



<center>圖 8</center>

3 Android JNI簡介及開發(fā)流程

3.1 JNI簡介

NDK的開發(fā)是基于JNI的。JNI是java語言提供的Java和C/C++相互溝通的機(jī)制,Java可以通過JNI調(diào)用本地的C/C++代碼,本地的C/C++的代碼也可以調(diào)用java代碼。JNI 是本地編程接口,Java和C/C++互相通過的接口。Java通過C/C++使用本地的代碼的一個(gè)關(guān)鍵性原因在于C/C++代碼的高效性。
JNI的開發(fā)流程如下;首先需要在Java中申明native方法,接著用C或者C++實(shí)現(xiàn)native方法,然后就可以編譯運(yùn)行了。
JNI primitive types (基本數(shù)據(jù)類型)映射參見下表;這些基本數(shù)據(jù)類型都是可以在Native層直接使用的 。


JNI reference types (引用數(shù)據(jù)類型)映射參見下表;引用數(shù)據(jù)類型則不能直接使用,需要根據(jù)JNI函數(shù)進(jìn)行相應(yīng)的轉(zhuǎn)換后,才能使用。

Java類型 Native Type 描述


3.2 JNI開發(fā)流程

  1. 在Java中聲明native方法
  2. 通過Java源文件得到class文件,然后通過javah命令導(dǎo)出JNI的頭文件
  3. 實(shí)現(xiàn)JNI方法
  4. 編譯so庫并在Java中調(diào)用

4 移植實(shí)戰(zhàn) 基于OpenCV的Libvisodo 向Android設(shè)備上的移植

4.1 移植環(huán)境搭建

我們將使用在第二節(jié)中創(chuàng)建的Visodo項(xiàng)目,我們已經(jīng)為其構(gòu)建完成了NDK的開發(fā)環(huán)境,由于我們的項(xiàng)目基于OpenCV,所以我們要為該項(xiàng)目添加OpenCV的相關(guān)引用。打開工程屬性,Project Properties -> C/C++ General -> Paths and Symbols為GNC C++編譯器添加如圖9所示路徑:



<center>圖 9</center>
如此,我們所有有關(guān)環(huán)境配置的工作就完成了。

4.2 相關(guān)代碼移植

我們需要在Visodo根目錄下新建一個(gè)jni目錄,在這個(gè)目錄中,我們會(huì)放置由C/C++編寫的相關(guān)代碼以及生成.so庫所需的相關(guān)配置文件。項(xiàng)目結(jié)構(gòu)如圖10所示。在Eclipse中使用NDK進(jìn)行編譯的時(shí)候,需要使用Android.mk和Application.mk兩個(gè)文件。
Android.mk中具體代碼如下所示。其中,LOCAL_MODULE表示模塊的名稱,LOCAL_SRC_FILES表示需要參與編譯的源文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
OPENCV_LIB_TYPE:=SHARED
include I:/OpenCV-android-sdk/sdk/native/jni/OpenCV.mk

LOCAL_SRC_FILES  := visodo.cpp
LOCAL_C_INCLUDES += $(LOCAL_PATH)
LOCAL_LDLIBS     += -llog -ldl
LOCAL_MODULE     := visodo
include $(BUILD_SHARED_LIBRARY)

Application.mk中具體代碼如下所示。其中,APP_ABI表示CPU的架構(gòu)平臺(tái)類型。

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a
APP_PLATFORM := android-8

<center>圖 10</center>
改寫已有的PC端的C/C++代碼,對外暴露接口以供Java調(diào)用。同時(shí)在Java處聲明這些接口的靜態(tài)方法,我們在Java處新建一個(gè)LibVisodo.java類,代碼如下:

package com.example.visodo;

public class LibVisodo {
    static {
        System.loadLibrary("opencv_java3");
        System.loadLibrary("visodo");
    }

    public static native String init(long firstPic, long secondPic ,boolean isFromCamera);

    public static native double[] start(long matAddrRgba, long afterPic, int i,double xx,double yy,double zz ,boolean isFromCamera);
    
    public static native void FindFeatures(long matAddrRgba);

}

在LibVisodo的頭部有一個(gè)加載動(dòng)態(tài)庫的過程,其中opencv_java3與visodo是so庫的標(biāo)識(shí),它們的完整名稱分別為libopencv_java3.so與libvisodo.so,這是加載so庫的規(guī)范。在上面的代碼中,聲明了三個(gè)native方法:init、start和FindFeatures,這三個(gè)就是需要在JNI中實(shí)現(xiàn)的方法。
我們通過編譯Java源文件得到class文件,可以直接在終端中操作,具體命令如下:

javac com/example/visodo/LibVisodo.java

成功后我們會(huì)得到一個(gè)LibVisodo.class的中間文件,之后我們可以通過javah命令導(dǎo)出JNI的頭文件,具體命令如下:
javah com.example.visodo.LibVisodo
同樣,我們在成功后會(huì)下當(dāng)前目錄下生成一個(gè)com_example_visodo_LibVisodo.h的頭文件,它是javah命令自動(dòng)生成的,內(nèi)容如下所示。當(dāng)然,我們也可以選擇手動(dòng)編寫該文件。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_visodo_LibVisodo */

#ifndef _Included_com_example_visodo_LibVisodo
#define _Included_com_example_visodo_LibVisodo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_visodo_LibVisodo
 * Method:    start
 * Signature: ()V
 */
JNIEXPORT jdoubleArray JNICALL Java_com_example_visodo_LibVisodo_start
  (JNIEnv * , jclass,jlong addrRgba,jlong afterPic,jint i ,jdouble xx,jdouble yy,jdouble zz ,jboolean isFromCamera);

JNIEXPORT jstring JNICALL Java_com_example_visodo_LibVisodo_init
  (JNIEnv * , jclass,jlong firstPic,jlong secondPic , jboolean isFromCamera);

JNIEXPORT void JNICALL Java_com_example_visodo_LibVisodo_FindFeatures(JNIEnv*, jobject,jlong addrRgba);

#ifdef __cplusplus
}
#endif
#endif

將生成的com_example_visodo_LibVisodo.h也放入jni目錄下,此時(shí)jni目錄結(jié)構(gòu)如圖11所示。visodo.cpp與vo_features.h文件為原有的C\C++編寫代碼修改后的文件。



<center>圖 11</center>
我們可以在Java中調(diào)用相關(guān)用C++編寫的方法,調(diào)用方式如下:

LibVisodo.init(firstPic.getNativeObjAddr(),
                    secondPic.getNativeObjAddr());
LibVisodo.start(mRgba.getNativeObjAddr(),
                    afterPic.getNativeObjAddr(),i);
LibVisodo.FindFeatures(afterPic.getNativeObjAddr());

編譯項(xiàng)目,編譯過程中Eclipse輸出日志如圖12所示。完成后obj文件下生成.so庫文件,如圖13:



<center>圖 12</center>


<center>圖 13</center>
完整項(xiàng)目可于github/mvo_android處下載。

文章完成時(shí)間比較早,當(dāng)時(shí)使用的還是Eclipse,后續(xù)如果大家有需求會(huì)考慮AndroidStudio實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容