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开发流程
- 在Java中声明native方法
- 通过Java源文件得到class文件,然后通过javah命令导出JNI的头文件
- 实现JNI方法
- 编译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实现