使用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++){ jobjectobj=(*env)->GetObjectArrayElement(env,array,i); jstringjstr=(*env)->GetObjectField(env,obj,_uri); LOGE( " =====uri===%s==== ",jstringtoChar(env,jstr)); } 上面使用到的jstringtoChar方法代码: /* * *jstringtochar */ char*jstringtoChar(JNIEnv*env,jstringjstr){ char*rtn=NULL; jclassclsstring=(*env)->FindClass(env, " java/lang/String "); jstringstrencode=(*env)->NewStringUTF(env, " utf-8 "); jmethodIDmid=(*env)->GetMethodID(env,clsstring, " getBytes ", " (Ljava/lang/String;)[B "); jbyteArraybarr=(jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); jsizealen=(*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); returnrtn; } 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 调用的方法体: staticJNINativeMethodmethods[]={ { " 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 Zjbooleanboolean Ijint int Jjlong long Djdouble double Fjfloat float Bjbyte byte Cjchar char Sjshort short 数组则以"["开始,用两个字符表示 [IjintArray int[] [FjfloatArray float[] [BjbyteArray byte[] [CjcharArray char[] [SjshortArray short[] [DjdoubleArray double[] [JjlongArray long[] [ZjbooleanArrayboolean[] 也可返回任意java对象,如上代码的()[Lcom/xuzhitech/xcloud/resource;代表一个不带参数的方法,并且返回java类中的Lcom/xuzhitech/xcloud/resource;数组。'['代表数组的意思,'L'代表类的意思 第三个参数为,第一个参数需要映射的本地c/c++对应的函数指针方法。 第四步 在加载jni的时候指定JNI版本并且通过传入进来的class路径注册Natives 方法 jintJNI_OnLoad(JavaVM*vm, void *reserved){ jintresult=JNI_ERR; JNIEnv*env=NULL; jclassclazz;intmethodsLenght;if((*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_4)!=JNI_OK){ LOGE("ERROR:GetEnvfailed\n");returnJNI_ERR; }//assert(env!=NULL);clazz=(*env)->FindClass(env,className);if(clazz==NULL){ LOGE("Nativeregistrationunabletofindclass'%s'",className);returnJNI_ERR; } methodsLenght=sizeof(methods)/sizeof(methods[0]);if((*env)->RegisterNatives(env,clazz,methods,methodsLenght)<0){ LOGE("RegisterNativesfailedfor'%s'",className);returnJNI_ERR; }//result=JNI_VERSION_1_4;returnresult; } 注意,使用Natives注册运行程序时,它会先检测jni与java类使用jni 类的native 方法是否相对应与一致,即使你没有使用到该 方法也会进行检测。 第五步 通过上面的修改,c/c++编写的jni 文件就可以不带一大串标准名称了,您可以像正常编写c一样编写你的jni函数,如下: /* *movefile */ jintdoMove(JNIEnv*env,jobjectthiz,jstringjsrc,jstringjdest,jstringjrename){ char*src=( char*)(*env)->GetStringUTFChars(env,jsrc,NULL); char*dest=( char*)(*env)->GetStringUTFChars(env,jdest,NULL); char*rename=( char*)(*env)->GetStringUTFChars(env,jrename,NULL); intreturnValue=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); returnreturnValue; } 注: JNI的一些基本方法的使用都可以参考这个网站:http://docs.oracle.com/javase/1.4.2/docs/guide/jni/spec/functions.html 本文转自 terry_龙 51CTO博客,原文链接:http://blog.51cto.com/terryblog/792442,如需转载请自行联系原作者