1

現在、JNI 環境とジョブジェクト オブジェクトをローカルに保存しています。JNI を ICS およびアップ デバイスで実行するには、JNI コードを修正する必要があることがわかりました。これは私が得るエラーです:

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 pageありthe oracle docsません。NewGlobalRef

編集

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

1 に答える 1

8

まず第一に:myEnv = (*env)->NewGlobalRef(env,obj);間違っています。この値をキャッシュしないでください

許可されているのは、メソッド ID、フィールド ID、クラス参照などをキャッシュすることです (ただし、後でこれらをクリーンアップするようにしてください)。ただし、これらの値をキャッシュするには特別な手段が必要です。

なんで?問題は、JVM がプログラムの必要に応じてクラスをロードおよびアンロードできることです。したがって、クラスの最後のインスタンスがガベージ コレクターによって破棄されるとすぐに、クラスがアンロードされる可能性があります。これが発生するとすぐに、キャッシュされた ID は無効になります。JVM がクラスを再度ロードした後、ID が同じになる可能性がありますが、これは保証されません。

解決策:これらの ID をキャッシュしたい場合は、クラスのアンロードが許可されていないことを JVM に伝える必要があります。これはまさに何をするかNewGlobalRefです。渡された参照の参照をインクリメントするだけなNewGlobalRefので、参照カウントがゼロになることはなく、参照された要素をグラベージ コレクションでクリーンアップすることはできません。

注意:の作成にNewGlobalRefは重大な欠点があります。Java 以外でDeleteGlobalRefは、参照のガベージ コレクションを再度有効にするために、この参照が不要になった場合に呼び出す必要があります。(ガベージ コレクターは、この参照がまだ必要かどうかを認識していないため) 言い換えると、ガベージを自分で確実にクリーンアップする必要があります。そうしないと、メモリ リークが発生します。

また、オブジェクトのグローバル参照を作成することはお勧めできません (オブジェクトを本当に維持したい場合を除きます)。これは、オブジェクトがガベージに入れられないため、決して解放されないことを意味します。

ベター バリアント:特定のオブジェクトへのアクセスを高速化するためにこれらの ID をキャッシュする場合は、( を使用してFindClass) クラスのグローバル参照を保持し、返されたクラス オブジェクトから ID を取得し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 の内部の仕組みについて少し洞察を与えることを願っています.

編集:OracleのWebサイトでJNI呼び出しに関するドキュメントを見つけることができます

于 2013-02-21T14:27:08.877 に答える