web-dev-qa-db-ja.com

Android JNIの任意のスレッドからのFindClass

AndroidのJNIヒントページにはこれが記載されています FAQ:FindClassが私のクラスを見つけられなかったのはなぜですか? 彼らは複数のソリューションに言及しており、最後のオプションはこれです

どこか便利なClassLoaderオブジェクトへの参照をキャッシュし、loadClass呼び出しを直接発行します。これには多少の努力が必要です。

だから、私はそれを機能させようとしましたが、この方法は単に何の役にも立たないようです。最終的に、ClassLoaderの使用方法を考え出しましたが、ネイティブスレッドから、まだタッチ/ロードされていないloadClassを試みると、動作しません。基本的に、ネイティブスレッドから呼び出されたときの動作はenv-> FindClassと同じです。ただし、アプリで既に使用されているクラスに対しては0を返しません。私が正しく理解できなかった場合、またはまだ使用/ロードされていないネイティブスレッドからクラスにアクセスすることができない場合のアイデア。






EDIT:正確に私が意味することを説明するために、さらに情報を提供します。通常のJNI env->FindClass(className)と、キャッシュされた_ClassLoader->loadClass_を使用するmyFindClass(env, className)を作成した別のJNIがあります。

ネイティブc/c ++からアクセスしようとしているクラスは「com/noname/TestClient」です。 myFindClass内では、env-> FindClassとそれが返すログ値も使用します。

_jclass myFindClass(JNIEnv * env, const char* name)
{
    ...
    jclass c0 = env->FindClass(name);
    jclass c1 = (jclass)env->CallObjectMethod(ClassLoader,
        MID_loadClass, envNewStringUTF(name));
    dlog("myFindClass(\"%s\") => c0:%p, c1:%p, c0 and c1 are same: %d",
        name, c0, c1, env->IsSameObject(c0, c1));
    ...
}
_

次に、問題を説明するこれらの3つの組み合わせがあります。

1)

_//inside JNI_OnLoad thread
myFindClass(env, "com/noname/TestClient");
...

//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
_

私はこのlogcatを取得します:

myFindClass( "com/noname/TestClent")=> c0:0x41b64558、c1:0x41b64558、c0およびc1は同じ:1
...
myFindClass( "com/noname/TestClent")=> c0:0、c1:0x41b64558、c0とc1は同じ:0

2)

_//inside JNI_OnLoad thread
env->FindClass("com/noname/TestClient");
...

//inside native thread created by pthread_create
myFindClass("com/noname/TestClient");
_

私はこのlogcatを取得します:

myFindClass( "com/noname/TestClent")=> c0:0、c1:0x41b64558、c0とc1は同じ:0

3)

_//inside JNI_OnLoad thread
//"com/noname/TestClient" isn't touched from JNI_OnLoad.
...

//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
_

私はこのlogcatを取得します:

myFindClass( "com/noname/TestClent")=> c0:0、c1:0、c0、c1は同じです:1

基本的に、私の問題は、ClassLoaderが3番目のケースでクラスを見つけられないことです。バグですか?問題を解決するために何ができますか?

EDIT2:さらに、ClassLoader :: loadClassは明らかにバグが多いようです。 myFindClass( "noname/TestClent")に問い合わせると、ガベージが返されます。返されたjclassを使用すると、アプリがクラッシュします。

41
Pavel

アプリを何度も試してクラッシュした後、同僚と私は別のネイティブスレッドでクラスローダーをキャッシュして正常に使用することができました。使用したコードを以下に示します(C++ 11ですが、C++ 2003に簡単に変換できます)。前述の「どこか便利なClassLoaderオブジェクトへの参照をキャッシュし、loadClassを発行する」直接呼び出します。これには多少の努力が必要です。」 findClassの呼び出しは、JNI_OnLoadのスレッドとは異なるスレッドから呼び出されたときに完全に機能しました。これがお役に立てば幸いです。

JavaVM* gJvm = nullptr;
static jobject gClassLoader;
static jmethodID gFindClassMethod;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
    gJvm = pjvm;  // cache the JavaVM pointer
    auto env = getEnv();
    //replace with one of your classes in the line below
    auto randomClass = env->FindClass("com/example/RandomClass");
    jclass classClass = env->GetObjectClass(randomClass);
    auto classLoaderClass = env->FindClass("Java/lang/ClassLoader");
    auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
                                             "()Ljava/lang/ClassLoader;");
    gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
    gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
                                    "(Ljava/lang/String;)Ljava/lang/Class;");

    return JNI_VERSION_1_6;
}

jclass findClass(const char* name) {
    return static_cast<jclass>(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
}

JNIEnv* getEnv() {
    JNIEnv *env;
    int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
    if(status < 0) {    
        status = gJvm->AttachCurrentThread(&env, NULL);
        if(status < 0) {        
            return nullptr;
        }
    }
    return env;
}
55
Átila Neves

最初にネイティブスレッドをJVMに接続してみてください。

JNI_OnLoadで最初に取得できるjvmへのポインター

env->GetJavaVM(&jvm);

次に、ネイティブスレッドから

JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);

次に、そのenvFindClassに使用します