Подтвердить что ты не робот

FindClass из любого потока в Android JNI

В разделе советов Android JNI упоминается этот FAQ: Почему FindClass не нашел мой класс? Они упоминают несколько решений, и последний вариант есть один:

Кэшировать ссылку на объект ClassLoader где-то удобно, и выдавать loadClass вызывает напрямую. Это требует определенных усилий.

Итак, я попытался заставить его работать, и кажется, что, несмотря ни на что, этот метод просто не работает для меня. В конце концов, я понял, как использовать ClassLoader, но он не будет работать, если из собственного потока я попытаюсь загрузить класс, который еще не был затронут/загружен. По сути, это идентично env- > FindClass в поведении при вызове из собственного потока, за исключением того, что он не будет возвращать 0 для классов, которые уже использовались в приложении. Любая идея, если бы я не понял это, или невозможно получить доступ к классам из собственного потока, которые еще не были загружены/загружены.






EDIT: Я расскажу больше, чтобы объяснить, что именно я имею в виду. Существует обычный JNI env->FindClass(className), а другой, который я написал myFindClass(env, className), который использует кешированный ClassLoader->loadClass.

Класс, который я пытаюсь получить из собственного 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 не находит мой класс в третьем случае. Это ошибка? Что можно сделать для устранения проблемы?

EDIT2: Кроме того, похоже, что ClassLoader:: loadClass явно глючит. Если я попрошу myFindClass ( "noname/TestClent" ), то он возвращает некоторый мусор, и когда я использую этот возвращенный jclass, приложение будет аварийно завершено.

4b9b3361

Ответ 1

После долгих попыток и сбоев моего приложения, коллеге и мне удалось кэшировать и успешно использовать загрузчик классов в другом, native, thread. Код, который мы использовали, показан ниже (С++ 11, но легко преобразован в С++ 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;
}

Ответ 2

Сначала попробуйте подключить свой собственный поток к JVM.

Указатель на jvm вы можете получить первую вещь в JNI_OnLoad

env->GetJavaVM(&jvm);

Затем из вашего собственного потока

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

Затем используйте env для FindClass