FindClass from any thread in Android JNI

Android’s JNI tips page mentions this FAQ: Why didn’t FindClass find my class?
They mention multiple solutions and the last option there is this one:

Cache a reference to the ClassLoader object somewhere handy, and issue
loadClass calls directly. This requires some effort.

So, I tried to get it working and it seems that no matter what, this method simply does not work for me. Eventually, I figured how to use ClassLoader but it won’t work if from a native thread I try to loadClass that hasn’t been touched/loaded yet. Essentially, it’s the identical to env->FindClass in behavior when called from a native thread, with the exception that it won’t return 0 for classes that were already use in the app. Any idea if I didn’t get it right, or it’s impossible to access classes from a native thread that weren’t used/loaded yet.

EDIT: I’ll give more info to explain what exactly I mean. There is regular JNI env->FindClass(className), and another one that I wrote myFindClass(env, className) that uses cached ClassLoader->loadClass.

The class that I’m trying to access from native c/c++ is “com/noname/TestClient”. Inside myFindClass I also use env->FindClass and log value that it returns:

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

Then, I have these 3 combinations to explain the issue.

1)

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

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

I get this logcat:

myFindClass(“com/noname/TestClent”) => c0:0x41b64558, c1:0x41b64558,
c0 and c1 are same: 1

myFindClass(“com/noname/TestClent”) => c0:0,
c1:0x41b64558, c0 and c1 are same: 0

2)

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

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

I get this logcat:

myFindClass(“com/noname/TestClent”) => c0:0, c1:0x41b64558, c0 and c1 are same: 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");

I get this logcat:

myFindClass(“com/noname/TestClent”) => c0:0, c1:0, c0 and c1 are same: 1

Basically, my issue is that ClassLoader doesn’t find my class in the 3rd case. Is it a bug? What can be done to fix the problem?

EDIT2:
On top of that, it seems that ClassLoader::loadClass is plainly buggy. If I ask myFindClass(“noname/TestClent”) then it returns some garbage, and when I use that returned jclass in any way the app crashes.

3

  • Yes, that’s normal, Android apps do not use the system class loader by default. Just cache all that you need in JNI_OnLoad() and that will take care of that.


    Commented
    Nov 7, 2012 at 13:24

  • 1

    Re: EDIT2: sounds like the method threw an exception, at which point the return value is undefined. loadClass() never returns null; it either returns the class reference or it throws an exception.

    – fadden


    Commented
    May 9, 2013 at 16:39

  • @fadden it could be. I don’t remember exactly what was the issue, but I solved it eventually.

    – Pavel P


    Commented
    May 9, 2013 at 19:41

            

2 Answers 2

                        

After much trying and crashing of my app, a colleague and I managed to cache and succesfully use the class loader in another, native, thread. The code we used is shown below (C++11, but easily converted to C++2003), posted here since we couldn’t find any examples of the aforementioned “Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.”. Calling findClass worked perfectly when called from a thread different from the one of JNI_OnLoad. I hope this helps.

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(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;
}

15

  • 7

    ClassLoader.loadClass() does not return null when the class is not found. It throws an exception, which means the return value is undefined. You must check for exceptions after calling CallObjectMethod, and you must not use the return value if an exception has been raised. (This is true in general — any Call*Method invocation should be followed by ExceptionCheck or ExceptionOccurred, and you can see the exception in the log with ExceptionDescribe.)

    – fadden


    Commented
    May 9, 2013 at 16:37

  • 6

    I understand; I just wanted to make sure that anybody who did copy it added the necessary checks to JNI_OnLoad and findClass (especially the latter — you could convert an exception to NULL there, and clear the exception, to get the semantics you want). Note also you don’t need to call FindClass to find java.lang.Class; you can just use GetObjectClass on the jclass you already have (it’s faster and never fails on a valid object). Have you tried this on Android >= 4.0? Looks like you need a NewGlobalRef on gFindClassMethod.

    – fadden


    Commented
    May 13, 2013 at 16:35

  • 1

    Cool. FWIW, I meant gClassLoader — gFindClassMethod is a jmethodID.

    – fadden


    Commented
    May 28, 2013 at 16:32

  • 14

    Excelent answer… Just a comment, this didn’t worked for me because of this : stackoverflow.com/questions/14765776/…. After calling NewGlobalRef() on the gClassLoader object, this solved my problem. Thanks!

    – LarryPel


    Commented
    Sep 24, 2013 at 2:22

  • 7

    thanks for the solution. note you have to call NewGlobalRef() BEFORE this gets stored globally. calling NewGlobalRef() from a different thread isn’t going to work. Do it like such: gClassLoader = env->NewGlobalRef(env->CallObjectMethod(myClass, getClassLoaderMethod)); For more details check here: android-developers.blogspot.kr/2011/11/…


    Commented
    Oct 14, 2015 at 2:00



Try attaching your native thread to the JVM first.

The pointer to jvm you can obtain first thing in JNI_OnLoad

env->GetJavaVM(&jvm);

Then from your native thread

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

Then use that env for FindClass

1

  • 9

    Alex, off course I do that, without it 1) and 2) wouldn’t work 🙂

    – Pavel P


    Commented
    Nov 7, 2012 at 6:27

                    

                        

Not the answer you’re looking for? Browse other questions tagged

or ask your own question.

            
文章来源于互联网:FindClass from any thread in Android JNI