1. 程式人生 > >JNI DETECTED ERROR IN APPLICATION解決記錄

JNI DETECTED ERROR IN APPLICATION解決記錄

       最近遇到一個JNI的問題,同一套程式碼在Android4.4版本前的裝置上執行是OK的,但是在Android5.0之後的裝置上就會崩潰,檢視logcat發現報JNI DETECTED ERROR IN APPLICATION錯誤。

      (1)第一個錯誤:

               JNI DETECTED ERROR IN APPLICATION: calling static method xxxx with CallxxxxMethodV in call to CallxxxxMethodV

       有過JNI程式設計經驗的就會知道,呼叫方法分static和非static兩種,分別會用到

                    GetStaticMethodID                                    GetMethodID    

                    CallStatic<type>Method                            Call<type>Method

                    其中<type>指Int, Long, Char等型別

       問題的原因就是編寫JNI程式碼的同事混用了這兩種方式,先用了GetStaticMethodID,然後又用的非static的CallIntMethod方法,Android4.4之前版本JNI檢查機制沒有Android5.0之後的版本嚴格,所以沒有報錯,程式也不會崩潰,但正確的方式應該是GetMethodID+Call<type>Method,GetStaticMethodID+CallStatic<type>Method,這個問題解決後又接著又報出了下面的錯誤。

      (2)第二個錯誤:

               JNI DETECTED ERROR IN APPLICATION: native code passing in reference to invalid stack indirect reference table or invalid reference: 0xfff5f1b0
               in call to CallVoidMethod
 <==這一行報錯是問題的切入點

       這個錯誤顯示是CallVoidMethod的引數非法引用,網上找了一些相關問題的帖子,結合原生代碼,發現最有可能的就是執行緒間不能直接傳遞JNIEnv和jobject這類執行緒專屬屬性值導致,要知道JavaVM是屬於java程序的,每個程序只有一個JavaVM,而這個JavaVM可以被多執行緒共享,但是JNIEnv和jobject是屬於執行緒私有的,不能共享,那有什麼辦法解決呢?這裡我們不講JNIEnv的執行緒間傳遞,有興趣的網上可以找到相關帖子,而jobject可以用全域性引用的方式在多執行緒間使用,舉個簡單的例子:

#Include <jni.h>
#include <pthread.h>
pthread_t pthread;
jobject object;
JavaVM* jvm;
void *run(void *arg)
{
    JNIEnv * jenv;
    jvm->AttachCurrentThread(&jenv, NULL);
    jenv->CallVoidMethod(object, jnv->GetMethodID(jnv->GetObjectClass(object), "foo", "()V"));
    jenv->DeleteGlobalRef(object);
    jvm->DetachCurrentThread();
    return NULL;
}  

void Java_com_program_Initialize(JNIEnv*jenv, jobject caller)
{   /*要想在新執行緒中使用物件caller,就必須以全域性引用方式儲存,否則caller只是區域性引用,本方法返回後就會銷燬*/
    object = jnv->NewGlobalRef(caller); 
    jenv->GetJavaVM(&jvm);
    pthread_create(&thread, NULL, run, NULL);
}
           再回到前面的問題,按照這個線索,我在本地JNI程式碼裡發現在pthread_create建立新執行緒之前,僅僅將jobject儲存在了一個全域性變數裡面,而沒有使用全域性引用,以上面的程式碼為例,即本地JNI程式碼裡進行了類似object = caller;的賦值,這顯然是沒有用的,一旦函式返回,caller就會被GC回收銷燬,object指向的就是一個非法地址,最終導致上面的JNI錯誤。