美文网首页
Android端通过JNI调用OpenCV库详解

Android端通过JNI调用OpenCV库详解

作者: Echopppppp | 来源:发表于2018-03-21 10:58 被阅读338次

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实现

相关文章

网友评论

      本文标题:Android端通过JNI调用OpenCV库详解

      本文链接:https://www.haomeiwen.com/subject/qfvwqftx.html