web-dev-qa-db-fra.com

FindClass à partir de n'importe quel thread dans Android JNI

La page de conseils JNI d'Android le mentionne FAQ: Pourquoi FindClass n'a-t-il pas trouvé ma classe? Ils mentionnent plusieurs solutions et la dernière option est celle-ci:

Mettez en cache une référence à l'objet ClassLoader quelque part à portée de main et émettez des appels loadClass directement. Cela nécessite un certain effort.

J'ai donc essayé de le faire fonctionner et il semble que, quoi qu'il arrive, cette méthode ne fonctionne tout simplement pas pour moi. Finalement, j'ai compris comment utiliser ClassLoader mais cela ne fonctionnera pas si à partir d'un thread natif j'essaie de chargerClass qui n'a pas encore été touché/chargé. Essentiellement, c'est identique à env-> FindClass dans le comportement lorsqu'il est appelé à partir d'un thread natif, à l'exception qu'il ne renverra pas 0 pour les classes qui étaient déjà utilisées dans l'application. Toute idée si je ne l'ai pas bien fait, ou s'il est impossible d'accéder aux classes à partir d'un thread natif qui n'ont pas encore été utilisés/chargés.






EDIT: Je vais donner plus d'informations pour expliquer exactement ce que je veux dire. Il existe régulièrement JNI env->FindClass(className), et un autre que j'ai écrit myFindClass(env, className) qui utilise le cache ClassLoader->loadClass.

La classe à laquelle j'essaie d'accéder à partir de c/c ++ natif est "com/noname/TestClient". Dans myFindClass, j'utilise également env-> FindClass et la valeur de journal qu'elle renvoie:

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));
    ...
}

Ensuite, j'ai ces 3 combinaisons pour expliquer le problème.

1)

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

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

Je reçois ce logcat:

myFindClass ("com/noname/TestClent") => c0: 0x41b64558, c1: 0x41b64558, c0 et c1 sont identiques: 1
...
myFindClass ("com/noname/TestClent") => c0: 0, c1: 0x41b64558, c0 et c1 sont identiques: 0

2)

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

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

Je reçois ce logcat:

myFindClass ("com/noname/TestClent") => c0: 0, c1: 0x41b64558, c0 et c1 sont identiques: 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");

Je reçois ce logcat:

myFindClass ("com/noname/TestClent") => c0: 0, c1: 0, c0 et c1 sont identiques: 1

Fondamentalement, mon problème est que ClassLoader ne trouve pas ma classe dans le 3ème cas. Est-ce un bug? Que peut-on faire pour résoudre le problème?

EDIT2: En plus de cela, il semble que ClassLoader :: loadClass est clairement bogué. Si je demande à myFindClass ("noname/TestClent"), il renvoie des ordures, et lorsque j'utilise ce jclass retourné, l'application se bloque.

41
Pavel

Après beaucoup d'essais et de plantage de mon application, un collègue et moi avons réussi à mettre en cache et à utiliser avec succès le chargeur de classe dans un autre thread natif. Le code que nous avons utilisé est illustré ci-dessous (C++ 11, mais facilement converti en C++ 2003), publié ici car nous n'avons trouvé aucun exemple de "Cache une référence à l'objet ClassLoader susmentionné quelque part à portée de main, et émettez loadClass appelle directement. Cela nécessite un certain effort. ". L'appel à findClass fonctionnait parfaitement lorsqu'il était appelé à partir d'un thread différent de celui de JNI_OnLoad. J'espère que ça aide.

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

Essayez d'abord d'attacher votre thread natif à la JVM.

Le pointeur vers jvm, vous pouvez obtenir la première chose dans JNI_OnLoad

env->GetJavaVM(&jvm);

Puis à partir de votre thread natif

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

Utilisez ensuite ce env pour FindClass

6