2013-02-21 23 views
1

我有我的JNI環境和jobject對象保存到現在爲止。我發現對於我的JNI來運行ICS和up設備,我需要修復我的JNI代碼。這是錯誤我得到:JNIEnv全局引用如何與C中的jobject不同?

02-20 10:20:59.523: E/dalvikvm(21629): JNI ERROR (app bug): attempt to use stale local reference 0x38100019 
02-20 10:20:59.523: E/dalvikvm(21629): VM aborting 
02-20 10:20:59.523: A/libc(21629): Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1), thread 21629 

我感到困惑如何創建/摧毀這些全局變量,如果我連這樣做的權利。

我的應用程序目前運行在使用該代碼的所有ICS預設備罰款:

BYTE Java_my_eti_commander_RelayAPIModel_00024NativeCalls_InitRelayJava(JNIEnv *env, jobject obj ) { 

    myEnv = (env); 
    myObject = obj; 

    changeID = (*myEnv)->GetStaticMethodID(myEnv, myObject, "changeItJavaWrapper", "(S)V" ); 
    getID = (*myEnv)->GetStaticMethodID(myEnv, myObject, "getItJavaWrapper" , "(S)S" ); 
    putID = (*myEnv)->GetStaticMethodID(myEnv, myObject, "putItJavaWrapper" , "(B)V"); 
    flushID = (*myEnv)->GetStaticMethodID(myEnv, myObject, "flushItJavaWrapper" , "()V" ); 
    delayID = (*myEnv)->GetStaticMethodID(myEnv, myObject, "delayItJavaWrapper" , "(S)V" ); 

    RelayAPI_SetBaud= WrapSetBaud; 
    RelayAPI_get = WrapGetIt; 
    RelayAPI_put = WrapPutIt; 
    RelayAPI_flush = WrapFlushIt; 
    RelayAPI_delay = WrapDelayIt; 
    ... 
} 

GetStaticMethodID來電,RelayAPI_變量是導致這裏所有的函數指針:

void WrapSetBaud(WORD w) { 
    return (*myEnv)->CallStaticVoidMethod(myEnv, myObject, changeID, w); 
} 

short WrapGetIt(WORD time) { 
    return (*myEnv)->CallStaticShortMethod(myEnv, myObject, getID, time); 
} 

void WrapPutIt(BYTE buff) { 
    return (*myEnv)->CallStaticVoidMethod(myEnv, myObject, putID, buff); 
} 

void WrapFlushIt(void) { 
    return (*myEnv)->CallStaticVoidMethod(myEnv, myObject, flushID); 
} 

void WrapDelayIt(WORD wait) { 
    return (*myEnv)->CallStaticVoidMethod(myEnv, myObject, delayID, wait); 
} 

最後,它返回到我的Java代碼在這裏:

public static void changeItJavaWrapper(short l) throws IOException { 
    mModelService.changeitJava(l); 
} 

public static void flushItJavaWrapper() { 
    mModelService.flushitJava(); 
} 

public static void putItJavaWrapper(byte p) { 
    mModelService.putitJava(p); 
} 

public static void delayItJavaWrapper(short wait) { 
    mModelService.delayitJava(wait); 
} 

public static short getItJavaWrapper(short s) throws IOException { 
    return mModelService.getitJava(s); 
} 

我有改變了我的初始化到:

myEnv = (*env)->NewGlobalRef(env,obj); 
myObject = (*env)->NewGlobalRef(env,obj); 

但我是這個極其混亂,因爲它們具有相同的參數,它只是沒有任何意義。我無法找到這種方法的文檔,因爲這聽起來很愚蠢,this tutorial,this pagethe oracle docs沒有關於NewGlobalRef方法本身的任何信息。

編輯

jmethodID changeID; 
jmethodID getID; 
jmethodID putID; 
jmethodID flushID; 
jmethodID delayID; 
jobject myObject; 
jclass bluetoothClass; 
JNIEnv *myEnv; 

回答

7

首先:myEnv = (*env)->NewGlobalRef(env,obj);是錯誤的。 You mustn't cache this value

你允許的是緩存方法ID,字段ID,類引用,......(但確保你清理了這些東西后)。但緩存這些值需要特殊的措施。

爲什麼?問題是允許JVM根據程序的需要加載和卸載類。因此,一旦類的最後一個實例被垃圾收集器銷燬,就可能發生類被卸載。只要發生這種情況,您的緩存ID就不再有效。在JVM再次加載類之後,ID可能會相同,但這不能保證。

解決方案:如果你想緩存這些ID,你必須告訴JVM它不允許卸載一個類。這正是NewGlobalRef所做的。您只需遞增傳遞到NewGlobalRef的引用的引用,以便引用計數永遠不會降至零,並且不允許垃圾收集清除引用的元素。

注意:創建NewGlobalRef具有嚴重的缺陷:除了在Java中,你必須確保你打電話DeleteGlobalRef,如果你不以重新啓用參考的垃圾收集不再需要此引用。 (因爲垃圾收集器沒有意識到你仍然需要這個參考或換句話說:你必須確保你自己清理垃圾,否則你會留下內存泄漏。

我也想說,創建一個對象的全局引用不是一個好主意(除非你真的想保持對象活着),因爲這意味着對象將永遠不會進入垃圾,因此永遠不會釋放。

更好的方法:如果您想以此來加速比訪問某個對象緩存這些ID,保持了類(使用FindClass)一個全球性的參考和從類對象搶標識FindClass回報。

下面是我的意思(不完整)例子。我通常創建一個結構,它包含我需要訪問類的所有ID,以保持我的名稱空間清潔。

/*! \brief Holds cached field IDs for MyClass.java */ 
typedef struct MyClass { 
    int loaded; /*!< Nonzero if the information are valid */ 

    jclass clazz; /*!< Holds a global ref for the class */ 
    jfieldID aField; /*!< Holds the field ID of aField */ 
}tMyClass; 

static tMyClass me = { 0 }; 

最簡單的方法是提供您的對象,它的確上面定義的結構的初始化一個「連接」功能:可以按如下想像這一點。

/*! \brief This function fetches field IDs for a specific class in order to have 
      faster access elsewhere in the code 

    \param env a valid JNI environment 

    \return 
      - 0 if OK 
      - <0 if an error occured */ 
int MyClass_connect(JNIEnv *env) 
{ 
    jobject theClass = env->FindClass("MyClass"); 
    if (theClass == NULL) goto no_class; 

    me.clazz = (jclass) env->NewGlobalRef(theClass); // make it global to avoid class unloading and therefore 
                // invalidating the references obtained. 
    if (me.clazz == NULL) goto no_memory; 

     me.aField = env->GetFieldID(me.clazz, "aField", "I") ; 
    if (me.aField == NULL) goto no_aField; 

    me.loaded = 1; 
    return 0; 

no_aField: 
    env->DeleteGlobalRef(me.clazz); 
no_memory: 
no_class: 
    return -1; 
} 

調用MyClass_connect成功,您可以使用me.aField縮短代碼訪問代碼中的後場。當然,你必須提供當不再需要MyClass的被稱爲斷開功能:

void MyClass_disconnect(JNIEnv *env) 
{ 
    if (me.loaded == 0) return; 

    env->DeleteGlobalRef(me.clazz); 
    memset(me, 0, sizeof(tMyClass)); 
} 

對不起,這個有點長的帖子,但我希望這可以幫助解決你的困惑了一下,給你一個有點了解JNI的內部運作情況以及如何有效處理這個問題。

編輯:您可以找到文檔中關於JNI的oracle's website

+0

非常感謝這個要求。你碰巧知道任何有關C的更多信息的好網站嗎? – JuiCe 2013-02-21 14:32:02

+0

您正在尋找什麼信息? JNI?最佳實踐? – junix 2013-02-21 14:36:22

+0

我仍然有點迷路,因爲我使用'GetStaticMethodID'而不是'GetFieldID'。對於我後來在代碼中調用的方法,我還必須具有全局保存的JNI Env,如我的第二個代碼塊所示。 - 我也不知道你的tUsb_Device變量是什麼,那個參數的數字與什麼比較? – JuiCe 2013-02-22 15:04:27

相關問題