1. 程式人生 > >Android JNI錯誤--原因和解決辦法

Android JNI錯誤--原因和解決辦法

這兩天寫程式,碰到JNI錯誤,具體的錯誤如下: JNI ERROR(app bug):accessed stale local reference 0x1d300009 (index 2 in a table of size 0) VM aborting Fatal singal 6 (SIGABRT) at 0x000028b3 (code=-6),thread 10501 (Thread -7129) 折騰了一天,在網上找了一些資料,弄明白原因,現在跟大家一起分享一下。 早前的Android版本,雖然用的是直接指標,但是GC(garbage collector 垃圾回收器,下面簡稱GC)不會隨便回收物件,其實這樣的做法是不安全的。但是從Android 4.0+開始,修改了GC。GC現在的工作模式是:全域性物件使用過程中可能會被回收。不同於之前的使用直接指標,現在是通過一個 handle (指標的引用)去操作。當 GC 回收物件時,只需要修改這張 handle 表即可,而表的維護工作顯然交給底層就好了。
出現這個問題怎麼解決呢?有連個方法去解決: 1.android 還是提供了JNI bug的相容模式:在AndroidManifest.xml中將targetSdkVersion的版本改在14以下(也就是Android 4.0以下),這樣就可以避免這個問題了。 2.修改 JNI 層程式碼原來直接通過FindClass()函式得到 static 物件的地方:
jclassRef = jniEnv->FindClass("android/graphics/BitmapFactory$Options");
改為通過呼叫 NewGlobalRef() 函式得到全域性變數的引用,然後強制轉換下型別:
jclass tmpClass = jniEnv->FindClass("android/graphics/BitmapFactory$Options"); 
jclassRef = (jclass)jniEnv->NewGlobalRef(tmpClass);

這個方法改正過後,targetSdkVersion的版本就可以改成14以上了。 下面再來說下關於JNI引用的一些知識:

在JNI中,有一些不同的引用。其中最重要的兩種就是區域性引用(local references)和全域性引用(global references)。任意一個給定的object都可以是區域性或是全域性的。全域性或者區域性的區別同時影響生命週期和作用域。全域性的可以在任意執行緒通過本執行緒的JNIEnv*使用,並且可以有效到明確呼叫DeleteGlobalRef()之時。區域性的只能在其最初被遞交到的執行緒中使用,並且可以有效到明確呼叫DeleteLocalRef()之時,或者,更普遍的,到你從你的原生函式中返回為止。當原生函式返回時,所有的區域性引用都會被自動刪除掉。

在之前的系統中,區域性引用是直接的指標,區域性引用永遠不會真正變為不可用的。那就意味著你可以無限使用一個區域性引用,即使你已經明確對它呼叫過DeleteLocalRef()了,或者使用PopLocalFrame()明確刪除了它。

雖然任意JNIEnv*只能在一個執行緒中可用,但由於Android在JNIEnv*中從來沒有儲存過每個執行緒的狀態,所以之前在錯誤的執行緒中使用JNIEnv*也不會出問題。現在每個執行緒都有一個區域性引用表,在正確的執行緒中使用JNIEnv*就是至關重要的了。