Android端通过JNI调用OpenCV库详解

1 Android NDK 应用场景

当我们已有在其他平台上编写的C或C++代码时,我们可以使用NDK(Native Development Kit)在Android平台中生成相应的.so库并调用。Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C/C++之类的原生代码语言执行部分程序。除此以外,NDK还提供了对代码的保护作用,这是由于apk的java层代码很容易被反编译,而C/C++库反编难度较大。Android NDK APP的主要构成如图所示。

Android NDK对于Android SDK只是个组件,它可以帮我们生成的JNI兼容的共享库可以在大于Android1.5平台的ARM CPU上运行,将生成的共享库拷贝到合适的程序工程路径的位置上,以保证它们自动的添加到你的apk包中(并且签名的)。而且,Android NDK还提供了

  • 一组交叉编译链(编译器、链接器等)来生成可以在Linux,OS X和Windows运行的二进制文件;

  • 一组与由Android平台提供的稳定的本地API列表的头文件,它们在docs/STABLE-APIS.html中有说明;

  • 一个编译系统(build system)可以允许开发者写一个非常短的编译文件(build files)去描述哪个源代码需要编译,并且怎样编译。编译系统可以解决所有的toolchain/platform/CPU/ABI细节的问题。并且,较晚的NDK版本中还添加了更多的可以不用改变开发者的编译文件的情况下的toolchains、platforms、系统接口。
    通过以上的叙述,我们知道Android NDK解决了核心模块使用托管语言开发执行效率低下的问题;允许程序开发人员直接使用C/C++源代码,极大的提高了Android应用程序开发的灵活性。
    但同时Android NDK也存在着一些不足。
    NDK并不是一个可以编写通用的源代码并且可以在Android设备上运行的方法,你的应用程序还是需要使用JAVA程序,适当的处理系统事件来避免“应用程序没有反应”的对话框或者处理Android应用程序的生命周期。注意:可以适当的在源代码中写一个复杂的应用程序,用于启动/停止一个小型的“应用程序包”。
    NDK在Android平台仅仅提供了有限的本地API和库文件的支持的系统头文件,然而一个标准的Android系统镜像包括许多本地共享库,这些都应该被考虑在更新和发行版本的可以彻底改变的实现细节。如果Android系统库没有明确的被NDK明确的支持,然后应用程序不应该依赖于它提供的,或者打破了将来在各种设备上的无线系统更新,选定的系统库将逐渐被添加到稳定的NDK API中。

2 Android NDK应用的开发环境搭建

2.1 Android Eclipse环境搭建

我的的开发环境基于Eclipse。首先,我们需要到Android官网下载Android的开发工具ADT(Android Development Tool的缩写),该工具集成了最新的ADT和NDK插件以及Eclipse,该环境满足传统Android应用(Android SDK APP)开发环境。为了让我们的开发可以编译C/C++代码,我们需要为其安装CDT插件,安装完毕后打开Help->About Eclipse 如图所示。

2.2 Android NDK 环境搭建

首先从Android官网上下载NDK,我们选择的版本是android-ndk-r10e。下载完成后,将NDK安装至任意目录下。
打开Eclipse并创建一个Android Application Project,我们将其命名为Visodo。完成后其项目结构如图3所示。选中该项目,右击进入选择”Properties”,界面如图4所示。这是典型Android SDK APP的配置目录,我们可以发现没有任何和C/C++相关的目录。
我们需要将Android项目转换为C\C++项目(使其具备C++属性),右击New -> Other -> C/C++ -> Convert to a C/C++ Project。,此时Properties界面如图5所示,从图上可见,已有了C/C++的相关属性。因而,我们也可以开展对NDK相关属性的配置工作了。


<center>图3</center>


<center>图 4</center>


<center>图 5</center>
配置NDK编译路径,Project->Properties-> C/C++Build,如图6,取消Use default command的勾选,其中Build-Command中ANDROID_NDK为环境变量中配置的Android-NDK路径;Build-Directory为当前工程目录。


<center>图 6</center>
进入Project->Properties-> C/C++Build->Environment,将NDK安装路径配置在NDKROOT中,如H:\BaiduYunDownload\android-ndk-r10e,见图7。


<center>图 7</center>
进入Project->Properties-> C/C++General->Paths and Symbols,进行NDK相关配置,如图8 。至此,Eclipse自动编译NDK的环境配置完成,我们可以在该项目中编写或使用已有C/C++代码。



<center>图 8</center>

3 Android JNI简介及开发流程

3.1 JNI简介

NDK的开发是基于JNI的。JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。
JNI的开发流程如下;首先需要在Java中申明native方法,接着用C或者C++实现native方法,然后就可以编译运行了。
JNI primitive types (基本数据类型)映射参见下表;这些基本数据类型都是可以在Native层直接使用的 。


JNI reference types (引用数据类型)映射参见下表;引用数据类型则不能直接使用,需要根据JNI函数进行相应的转换后,才能使用。

Java类型 Native Type 描述


3.2 JNI开发流程

  1. 在Java中声明native方法
  2. 通过Java源文件得到class文件,然后通过javah命令导出JNI的头文件
  3. 实现JNI方法
  4. 编译so库并在Java中调用

4 移植实战 基于OpenCV的Libvisodo 向Android设备上的移植

4.1 移植环境搭建

我们将使用在第二节中创建的Visodo项目,我们已经为其构建完成了NDK的开发环境,由于我们的项目基于OpenCV,所以我们要为该项目添加OpenCV的相关引用。打开工程属性,Project Properties -> C/C++ General -> Paths and Symbols为GNC C++编译器添加如图9所示路径:



<center>图 9</center>
如此,我们所有有关环境配置的工作就完成了。

4.2 相关代码移植

我们需要在Visodo根目录下新建一个jni目录,在这个目录中,我们会放置由C/C++编写的相关代码以及生成.so库所需的相关配置文件。项目结构如图10所示。在Eclipse中使用NDK进行编译的时候,需要使用Android.mk和Application.mk两个文件。
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的架构平台类型。

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

<center>图 10</center>
改写已有的PC端的C/C++代码,对外暴露接口以供Java调用。同时在Java处声明这些接口的静态方法,我们在Java处新建一个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的头部有一个加载动态库的过程,其中opencv_java3与visodo是so库的标识,它们的完整名称分别为libopencv_java3.so与libvisodo.so,这是加载so库的规范。在上面的代码中,声明了三个native方法:init、start和FindFeatures,这三个就是需要在JNI中实现的方法。
我们通过编译Java源文件得到class文件,可以直接在终端中操作,具体命令如下:

javac com/example/visodo/LibVisodo.java

成功后我们会得到一个LibVisodo.class的中间文件,之后我们可以通过javah命令导出JNI的头文件,具体命令如下:
javah com.example.visodo.LibVisodo
同样,我们在成功后会下当前目录下生成一个com_example_visodo_LibVisodo.h的头文件,它是javah命令自动生成的,内容如下所示。当然,我们也可以选择手动编写该文件。

/* 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目录下,此时jni目录结构如图11所示。visodo.cpp与vo_features.h文件为原有的C\C++编写代码修改后的文件。



<center>图 11</center>
我们可以在Java中调用相关用C++编写的方法,调用方式如下:

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

编译项目,编译过程中Eclipse输出日志如图12所示。完成后obj文件下生成.so库文件,如图13:



<center>图 12</center>


<center>图 13</center>
完整项目可于github/mvo_android处下载。

文章完成时间比较早,当时使用的还是Eclipse,后续如果大家有需求会考虑AndroidStudio实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容