背景: 最近一个月一直在做移植库的工作,将c代码到share library移植到Android平台。这就涉及到Android NDK(native develop kit)内容。这里只想记录下JNI(java native interface)经常遇到到问题。
   问题1.  忘记delete local reference。带New到方法(如:NewByteArray)这样到方法比较好辨认,需要手动调用DeleteLocalRef()来释放(返回值除外)。比较特殊的一个方法是:GetByteArrayELement必须要调用ReleaseByteArrayElements进行释放。当然如果你只是取bytearray中到byte,那么完全可以用GetByteArrayRegion实现。
    问题2. 没有NewGlobalRef。 在不同线程调用java方法,需要保存jobject对象,这时需要对jobject对象做全局引用,否则会失效。
    问题3.  jbytearray的length。在JNI layer获取到jbytearray到长度是不对到,应该由java获取byte[]的length再传给C layer。否则C layer有可能获得到是乱码。
    问题4.  线程问题。 不同线程使用JNIEnv*对象,需要AttachCurrentThread将env挂到当前线程,否则无法使用env。
    问题5.  javap 命令是对java的class文件操作;而javah命令需要在包名到上一层路径运行才行,否则无法生成.h文件。
    问题6. 尽量避免频繁调用JNI或者是使用JNI传输大量到数据。
    问题7. Reference Table overflow (max=1024) 或者是 Reference Table overflow (max=512)一定是因为忘记释放global reference或者local reference,请仔细检查代码。
    问题8. 不要在windows下使用cygwin编译NDK code,那样会遇到arguments too long问题,因为windows路径长度有限制导致。虽然可以使用subst将路径映射为短路径,但是在编译时间和调试上,windows的孩子都是伤不起。同样到build,在windows下要15分钟左右,而在mac下只要5分多,相差3倍。调试JNI 代码到速度更是不用提了,差太多。

    总结,JNI代码量其实不是很多,JNI作为一个数据传输层,它的作用仅仅是java和c直接到桥梁,但是如果处理不好将会是灾难,调试和找bug非常困难。




JNI学习记录
 
1.     JNI是Java与本地C/C++代码相互操作的一种方案。

2.     要使用JNI,需要:

1),在Java源程序中使用native关键字声明一个方法。

         如:public native void callCppFunction();

2),然后在命令行提示符下使用javah这个命令来生成关于JNI的本地头文件。

如:javah test.jni.MainArrayTest

         3),在VC Studio中建立一个DLL工程,拷贝这个头文件到该工程目录下,拷贝

         %JAVA_HOME%"include下面的jni.h、jni_md.h到工程目录下,或者加到系统目下,具体需要拷贝的头文件还要具体研究。

4),建一个cpp源文件,实现生成的头文件中声明的函数。

5),具体各种函数及数据类型的使用如下:具体自己研究:

l       简单方法调用,基本数据类型的使用

jclass clazz_TestNative = env->GetObjectClass(obj); 


 /*       


 jfieldID id_number = env->GetFieldID(clazz_TestNative, 


            "number", "I"); 


 jint number = env->GetIntField(obj, id_number); 


 cout<<number<<endl; 


 env->SetIntField(obj, id_number, 1000L); 


 */ 


 //        jmethodID id_max = env->GetMethodID(clazz_TestNative, "max", "(DD)D"); 


 //        jdouble maxValue = env->CallDoubleMethod(obj, id_max, 3.14, 3.15); 


 //        cout<<maxValue<<endl; 


 jfieldID id_person = env->GetFieldID(clazz_TestNative, "person", "Ltest/jni/Father;"); 


 jobject person = env->GetObjectField(obj, id_person); 


 jclass clazz_Father = env->FindClass("test/jni/Father"); 


 jmethodID id_Father_Function = env->GetMethodID(clazz_Father, "function", "()V"); 


 env->CallVoidMethod(person, id_Father_Function); 


 env->CallNonvirtualObjectMethod(person,clazz_Father, id_Father_Function); 


 l       对象的使用 


 jclass clazz_date = env->FindClass("java/util/Date"); 


 jmethodID outputDate_ID = env->GetMethodID(clazz_date, "<init>", "()V"); 


 jobject date_obj = env->NewObject(clazz_date, outputDate_ID); 


 jmethodID mid_date_getTime = env->GetMethodID(clazz_date, "getTime", "()J"); 


 unsigned times = env->CallLongMethod(date_obj, mid_date_getTime); 


 //        printf("%u",times); 


 cout<<times<<endl; 


 l       字符串的简单实用 


 jfieldID fid_msg = env->GetFieldID(env->GetObjectClass(obj), "message", "Ljava/lang/String;"); 


 jstring j_msg = (jstring)env->GetObjectField(obj, fid_msg); 


 //const jchar *p_jst = env->GetStringChars(j_msg, NULL); 


 //MessageBoxW(NULL,(const wchar_t *)p_jst, L"Title", MB_OK); 


 jint len = env->GetStringLength(j_msg); 


 jchar *j_buf = new jchar[len+1]; 


 j_buf[len] = L'"0'; 


 env->GetStringRegion(j_msg, 0, len, j_buf);       


 wstring wstr((const wchar_t *)j_buf); 


 //env->ReleaseStringChars(j_msg, j_buf);//不能释放字符数组啊 


 delete []j_buf; 


 std::reverse(wstr.begin(), wstr.end()); 


 jstring jnewStr = env->NewString((const jchar*)wstr.c_str(), wstr.size()); 


 env->SetObjectField(obj, fid_msg, jnewStr); 


                    


 l       数组的使用 


 jfieldID fid_arrays = env->GetFieldID(env->GetObjectClass(obj), "arrays", "[I"); 


 jintArray jint_arr = (jintArray)env->GetObjectField(obj, fid_arrays); 


 jint *int_arr = env->GetIntArrayElements(jint_arr, NULL); 


 jsize len = env->GetArrayLength(jint_arr); 


 for(int i = 0; i < len; ++i) 


 { 


 //       cout<<int_arr[i]<<endl; 


 } 


 sort(int_arr, int_arr + len, less_second); 


 env->ReleaseIntArrayElements(jint_arr, int_arr, /*JNI_ABORT/JNI_COMMIT/0*/0); 


 } 


 bool less_second(const int & m1, const int & m2) 


 { 


         return m1 > m2; 


 }



上面要注意在JNI中对Java中数据类型的映射,见头文件的声明。

还有注意Java中关于对象类型的表示方法比如:int I, boolean Z,

Object L<类的完整名称,注意包名以/分隔>;,数组[<数据类型>。

如果不知道可以使用javap这个反编译器。

        

         6)、更多资料,将Java手册。

         7)、编译,生成DLL。将DLL的文件路径加到Path环境变量下:

         8)、在Main函数中加载动态库。

如:System.loadLibrary("TestNative4"),注意不要DLL扩展名,为了跨平台嘛!

9)、如果使用的是eclipse之类的IDE工具,请重启,因为该类工具在启动时,默认读取了系统变量,但是不会监视变量的变化。

10)、注意在JNI中对Java对象的引用。不然垃圾回收将会产生问题。注意全局引用,局部引用、弱全局引用的使用。

           局部引用,也就是本地方法中返回引用。应该使用DeleteLocalRef释放该引用。NewLocalRef创建。还用很多方式创建。

           全局引用,需要手动释放,防止垃圾回收器回收。需要使用NewGlobalRef函数创建,释放它需要使用DeleteGlobalRef函数。

           弱全局引用,需要编程人员手动释放,但是它不防止垃圾回收器的回收。

           使用NewWeakGlobalRef创建,使用DeleteWeakGlobalref释放。IsSamObject(jobject obj1, jobject obj2)判断弱全局引用指向的对象是否已被回收。