It is well known a thread not generated from Java itself, like pthread_create
on android, or a native activity, can’t find a JVM method by calling Java env’s FindClass
. These method calls will return class not found (nullptr), even for prelude classes, like java.lang.String
.
Turns out findClass
method relies on a ClassLoader
to find an specific jclass
object. The solution, is simple, but subtle though.
Our native thread is not attached to any VM/Java Env and thus env->FindClass
calls will fail. We need another way to find classes. And what class has a FindClass method ? Well, no other class does. But a ClassLoader
exposes a loadClass
method, which indirectly calls findClass
and many other methods. Simply enough, we need to obtain a reference ClassLoader, being the most obvious java code to be: object.getClass().getClassLoader()
.
In my case, I get such class loader from the call to v8 initialisation. This method is public native void InitializeV8();
. In native:
JNIEXPORT void JNICALL Java_com_spellington_task_TaskRunnerInstance_InitializeV8(JNIEnv *env, jobject obj) { ... // obj if implicit this on java call to the native method. auto rc = env->GetObjectClass(obj); // obj.getClass() jclass clazz = env->GetObjectClass(rc); // find getClassLoader method jmethodID getClassLoaderMethod = env->GetMethodID(clazz, "getClassLoader", "()Ljava/lang/ClassLoader;"); // obtain class' ClassLoader reference. jobject objClassLoader = env->CallObjectMethod(rc, getClassLoaderMethod); // protect this ref from GC ! This is mandatory. // objClassLoader must be stored somewhere else for later usage objClassLoader = env->NewGlobalRef(objClassLoader); // ClassLoader class jclass objClassLoaderClass = env->FindClass("java/lang/ClassLoader"); // loadClass reference. // Must be stored somewhere else for later usage. jmethodID loadClassMethod = env->GetMethodID( objClassLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); }
This code just got a reference to a valid ClassLoader and a methodID to its loadClass method. Now we have an alternative to just calling findClass:
const char* const className = "com/spellington/HCSurfaceView"; auto clazz = env->FindClass(className); // if findClass failed, try the ClassLoader alternative. // This will be true in two different scenarios: // + className, effectively does not exist. // + a native thread calls into JNI if (clazz==nullptr) { // findClass alternative based on our ClassLoader clazz = static_cast<jclass>( env->CallObjectMethod( objClassLoader, loadClassMethod, env->NewStringUTF(className))); } // if clazz is nullptr, there another two different scenarios: // + most likely, className is not a valid fully qualified class name. // + a native thread is trying to find a class which does not exist in the ClassLoader, // but might exist on another ClassLoader. This is likely if and only if your app // has customs ClassLoader instances.
When using mixed Java/native thread, or just calling JNI in a multithread environment, you might want to pay attention to Java VM’s AttachCurrentThread
method… But that’s another story.