JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。以下介绍Android 中如何使用jni移植开源库的技巧.
Android.mk文件添加编译模块:
LOCAL_LDLIBS=-lm -llog
使用方法:
LOGE("%s",test);
第四步:
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode); //“jstr 为传入的字符串”调用getBytes方法
第五步:
jsize alen = (*env)->GetArrayLength(env,barr); //得到数组长度
第五步:
(*env)->SetObjectField(env,jobj,_modtime,jmodtime); //为对象中的每一列赋值。注意:如果JNI有提供的数据类型,可按提供的类型为对象中的列赋值,
如:(*env)->SetIntField(env,jobj,_vcr,current->is_vcr);
第六步:
(*env)->SetObjectArrayElement(env,array,i,jobj);将对象设置进第二步声明的数组中
DeleteLocalRef(env,jobj);//删除引用对象
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类型
V 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; }