使用NDK移植开源项目,JNI的使用技巧

简介:

 

Jni 的介绍

 JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。以下介绍Android 中如何使用jni移植开源库的技巧.

 JNI日志输出到Logcat中

#include <android/log.h>
#define LOG_TAG "===xcloud==="
#define LOGI(...)  android_log_print(ANDROID_LOG_INFO,LOG_TAG,VA_ARGS__)
#define LOGW(...)  android_log_print(ANDROID_LOG_WARN,LOG_TAG,VA_ARGS__)
#define LOGE(...)  android_log_print(ANDROID_LOG_ERROR,LOG_TAG,VA_ARGS__)

 

Android.mk文件添加编译模块:
LOCAL_LDLIBS=-lm -llog
使用方法:
LOGE("%s",test);

JNI调用Java方法

以调用String 的getBytes方法为例:
第一步:
jclass clsstring = (*env)->FindClass(env,"java/lang/String"); //找到Java String 类
第二步:
jstring strencode = (*env)->NewStringUTF(env,"utf-8");  //得到一个jstring 对象
第三步:
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"); //得到getBytes方法

 

第四步:
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode); //“jstr 为传入的字符串”调用getBytes方法

第五步:
jsize alen = (*env)->GetArrayLength(env,barr); //得到数组长度

 

jstring 转换 char*

char* test=(char*)(*env)->GetStringUTFChars(env,jstringVariable,NULL);

 

 

返回一个java对象数组

第一步:
jclass objectClass=(*env)->FindClass(env,"com/xuzhitech/xcloud/resource"); //找到对应的java 类(对象)
第二步:
jobjectArray array=(*env)->NewObjectArray(env,currentListCount,objectClass,NULL); //通过取到的java类(对象)创建一个指定固定大小的数组
第三步:
jfieldID _uri=(*env)->GetFieldID(env,objectClass,"uri","Ljava/lang/String;");//找到对象中的列
注意:在JNI中并未提供jstring 类型的对象,所以必须通过L指定包名找到该类,在有提供的类型中,可以直接使用该类型的大写首字母(jlong 需使用J)
如jint 类型可以如此编写:
jfieldID _vcr=(*env)->GetFieldID(env,objectClass,"is_vcr","I");对应的JNI提供类型可以参考如下  http://download.oracle.com/javase/1.4.2/docs/guide/jni/spec/functions.html
第四步:
jmethodID jid = (*env)->GetMethodID(env,objectClass,"<init>","()V");//"<init>"代表可以访问默认构造函数
jobject jobj=(*env)->NewObject(env,objectClass,jid); //通过第一步找到的jclass创建对应的对象

 

第五步:
(*env)->SetObjectField(env,jobj,_modtime,jmodtime); //为对象中的每一列赋值。注意:如果JNI有提供的数据类型,可按提供的类型为对象中的列赋值,
如:(*env)->SetIntField(env,jobj,_vcr,current->is_vcr);
第六步:
(*env)->SetObjectArrayElement(env,array,i,jobj);将对象设置进第二步声明的数组中
DeleteLocalRef(env,jobj);//删除引用对象

在JNI中循环读取对象数组

 

for (i =  0 ;i < currentListCount ;i++) {
jobject obj = (*env)->GetObjectArrayElement(env,array,i);
jstring jstr=(*env)->GetObjectField(env,obj,_uri);
LOGE( " =====uri===%s==== ",jstringtoChar(env,jstr));
}
上面使用到的jstringtoChar方法代码:
/* *
*jstring to char
*/
char* jstringtoChar(JNIEnv* env,jstring jstr){
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env, " java/lang/String ");
jstring strencode = (*env)->NewStringUTF(env, " utf-8 ");
jmethodID mid = (*env)->GetMethodID(env,clsstring,  " getBytes "" (Ljava/lang/String;)[B ");
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode);
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte* ba = (*env)->GetByteArrayElements(env,barr, JNI_FALSE);
if (alen >  0)
{
rtn = ( char*)malloc(alen +  1);
memcpy(rtn, ba, alen);
rtn[alen] =  0;
}
(*env)->ReleaseByteArrayElements(env,barr, ba,  0);
return rtn;

 

cpp JNI与 c JNI的主要注意事项:

用C编写jni文件,访问JNI内置提供方法格式:
(*env)->SetObjectArrayElement(env,array,i,jobj);
用CPP编写JNI文件,访问JNI内置提供方法格式:
env->SetObjectArrayElement(array,i,jobj);
另外,使用CPP编写的JNI代码,在调用C语言编写的库的时候,要添加以下代码,才可以正常使用(不然在链接的时候找不到相关接口:undefined reference.....):
ifdef __cplusplus
extern "C" {
#endif
... //引入的头文件
#ifdef __cplusplus
}
#endif

 

其他用法几乎一致。

 

JNI 使用Native注册

按照上面的方法写函数体,必须遵循JNI官方的一大堆标准进行方法的定义,有时候方法一多,不大好管理,也不利用查看,并且每次都要写一大堆恶心的标准方法名也不是一件好事。对此JNI有一套可以通过Native 注册的机制可以使用,以方便函数体的编写。

 

以下是Android 调用JNI注册Natives 的步骤:
第一步
声明Java中要调用jni 的类路径:
static const char *className="com/xuzhitech/xcloud/cadaver";
第二步
创建方法格式结构体:
struct JNINativeMethod {
const char* name;//method name 
const char* signature; //java method return value 
void* fnPtr;//c/c++ method 
} ;

第三步
使用结构体注册需要供Android 调用的方法体:

static JNINativeMethod methods[] = {
{ " StringTestOne "" ()Ljava/lang/String; ", ( void*)StringTestOne},
{ " executels ", " ()[Lcom/xuzhitech/xcloud/resource; ",( void*)executels},
{ " getProgress ", " ()Lcom/xuzhitech/xcloud/fileProgress; ",( void*)getProgress},
{ " getdownloadState ", " ()I ",( void*)getdownloadState},
{ " changeExecutable ", " (Ljava/lang/String;Ljava/lang/String;)I ",( void*)changeExecutable},
{ " Login ", " (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I ",( void*)Login},
{ " getCurrentListCount ", " ()I ",( void*)getCurrentListCount},
{ " mkcol ", " (Ljava/lang/String;)I ",( void*)mkcol},
{ " deleteFile ", " (Ljava/lang/String;)I ",( void*)deleteFile},
{ " deleteCol ", " (Ljava/lang/String;)I ",( void*)deleteCol},
{ " getFullUri ", " ()Ljava/lang/String; ",( void*)getFullUri},
{ " cdCommand ", " (Ljava/lang/String;)I ",( void*)cdCommand},
{ " logout ", " ()V ",( void*)logout},
{ " doCopy ", " (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I ",( void*)doCopy},
{ " doMove ", " (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I ",( void*)doMove},
{ " getFile ", " (Ljava/lang/String;Ljava/lang/String;)V ",( void*)getFile},
{ " putFile ", " (Ljava/lang/String;Ljava/lang/String;)V ",( void*)putFile},
{ " lock ", " (Ljava/lang/String;)I ",( void*) lock},
{ " unlock ", " (Ljava/lang/String;Ljava/lang/String;)I ",( void*)unlock},
{ " getToken ", " (Ljava/lang/String;)Ljava/lang/String; ",( void*)getToken},
{ " isSetToken ", " (Ljava/lang/String;)I ",( void*)isSetToken},

}; 

第一个参数为:Java实现要调用的方法名称
第二个参数为:该方法需要的返回值与方法参数,如->(方法参数)返回值,详细使用参考如下:
具体的每一个字符的对应关系如下

字符 Java类型 C类型

void  void
Z jboolean boolean
I jint  int
J jlong  long
D jdouble  double
F jfloat  float
B jbyte  byte
C jchar  char
S jshort  short

 

数组则以"["开始,用两个字符表示

 

[I jintArray  int[]
[F jfloatArray  float[]
[B jbyteArray  byte[]
[C jcharArray  char[]
[S jshortArray  short[]
[D jdoubleArray  double[]
[J jlongArray  long[]
[Z jbooleanArray boolean[]
也可返回任意java对象,如上代码的()[Lcom/xuzhitech/xcloud/resource;代表一个不带参数的方法,并且返回java类中的Lcom/xuzhitech/xcloud/resource;数组。'['代表数组的意思,'L'代表类的意思

 

第三个参数为,第一个参数需要映射的本地c/c++对应的函数指针方法。

第四步
在加载jni的时候指定JNI版本并且通过传入进来的class路径注册Natives 方法

 

jint JNI_OnLoad(JavaVM* vm,  void * reserved){ 

jint result = JNI_ERR;
JNIEnv* env = NULL;
jclass clazz;
int methodsLenght;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed\n");
return JNI_ERR;
}
// assert(env != NULL); 
clazz = (*env)->FindClass(env,className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'", className);
return JNI_ERR;
}
methodsLenght = sizeof(methods) / sizeof(methods[0]);
if ((*env)->RegisterNatives(env,clazz, methods, methodsLenght) < 0) {
LOGE("RegisterNatives failed for '%s'", className);
return JNI_ERR;
}
// 
result = JNI_VERSION_1_4;
return result;
}

 

 

注意,使用Natives注册运行程序时,它会先检测jni与java类使用jni 类的native 方法是否相对应与一致,即使你没有使用到该 方法也会进行检测。
第五步
通过上面的修改,c/c++编写的jni 文件就可以不带一大串标准名称了,您可以像正常编写c一样编写你的jni函数,如下:

/*
*move file
*/
jint doMove(JNIEnv* env,jobject thiz,jstring jsrc,jstring jdest,jstring jrename){
char* src=( char*)(*env)->GetStringUTFChars(env,jsrc,NULL);
char* dest=( char*)(*env)->GetStringUTFChars(env,jdest,NULL);
char* rename=( char*)(*env)->GetStringUTFChars(env,jrename,NULL);
int returnValue=multi_move(src,dest,rename);
//  free(src);
(*env)->ReleaseStringUTFChars(env,jsrc,src);
//  free(dest);
(*env)->ReleaseStringUTFChars(env,jdest,dest);
//  free(rename);
(*env)->ReleaseStringUTFChars(env,jrename,rename);
return returnValue;
}

 

 注:

JNI的一些基本方法的使用都可以参考这个网站:http://docs.oracle.com/javase/1.4.2/docs/guide/jni/spec/functions.html

 


 本文转自 terry_龙 51CTO博客,原文链接:http://blog.51cto.com/terryblog/792442,如需转载请自行联系原作者

相关文章
|
3天前
|
Java API Android开发
[NDK/JNI系列01] NDK与JNI的基本概念与使用场景
[NDK/JNI系列01] NDK与JNI的基本概念与使用场景
15 0
|
4天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
3天前
|
传感器 Java 开发工具
[NDK/JNI系列03] Android Studio集成NDK开发环境
[NDK/JNI系列03] Android Studio集成NDK开发环境
11 0
|
17天前
|
Java Linux 开发工具
NDK与JNI开发(1)ndk_build方式开发
NDK与JNI开发(1)ndk_build方式开发
NDK与JNI开发(1)ndk_build方式开发
|
6月前
|
移动开发 Java 开发工具
[笔记]Visual Studio 2015 开发安卓so库JNI层——HelloWorld
[笔记]Visual Studio 2015 开发安卓so库JNI层——HelloWorld
|
编解码 Java Android开发
so库你应该知道的基础知识
Android系统目前支持以下七种不同的CPU架构:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起),每一种都关联着一个相应的ABI。
213 0
|
缓存 Linux Go
Golang交叉编译(跨平台编译)的使用
存在交叉编译的情况时,cgo 工具是不可用的。在标准 go 命令的上下文环境中,交叉编译意味着程序构建环境的目标计算架构的标识与程序运行环境的目标计算架构的标识不同,或者程序构建环境的目标操作系统的标识与程序运行环境的目标操作系统的标识不同
324 0
Golang交叉编译(跨平台编译)的使用
|
Java API PHP
AndroidStudio反编译调试实战
AndroidStudio反编译调试实战
509 0
|
Java API Android开发
JNI开发环境和基础配置
JNI开发环境和基础配置
344 0
JNI开发环境和基础配置
|
自然语言处理 前端开发 安全
iOS-底层原理 31:LLVM编译流程 & Clang插件开发
iOS-底层原理 31:LLVM编译流程 & Clang插件开发
534 0
iOS-底层原理 31:LLVM编译流程 & Clang插件开发