env->FindClass does not work inside Juce app, very strange

If I alter Juce’s JNI_Onload to this:

extern "C" jint JNIEXPORT JNI_OnLoad (JavaVM* vm, void*)
{
    // Huh? JNI_OnLoad was called two times!
    jassert (androidJNIJavaVM == nullptr);

    androidJNIJavaVM = vm;

    auto* env = getEnv();

    // register the initialisation function
    auto juceJavaClass = env->FindClass("com/rmsl/juce/Java");
    __android_log_write(ANDROID_LOG_DEBUG, "Tag", "finding MainActivity");
    auto S = env->FindClass("com/my_app/flutter_app/MainActivity");
    __android_log_write(ANDROID_LOG_DEBUG, "Tag", "finding MainActivity END");
    if (!S) {
        __android_log_write(ANDROID_LOG_ERROR, "Tag", "finding MainActivity END");
    } else {
        __android_log_write(ANDROID_LOG_DEBUG, "Tag", "DID find MainActivity");
    }

it works without any problem. That is, the class MainActivity is found. However, the same line

auto mainActivityClass1 = env->FindClass("com/my_app/flutter_app/MainActivity");

on


MyJuceApp() {
        JNIEnv* env = juce::getEnv();
        auto mainActivityClass1 = env->FindClass("com/my_app/flutter_app/MainActivity");

(where START_JUCE_APPLICATION (MyJuceApp)

gives

A/zygote64: java_vm_ext.cc:523] JNI DETECTED ERROR IN APPLICATION: JNI CallBooleanMethodV called with pending exception java.lang.ClassNotFoundException: Didn't find class "com.my_app.flutter_app.MainActivity" on path: DexPathList[[dex file "InMemoryDexFile[cookie=[0, 547943543104]]"],nativeLibraryDirectories=[/system/lib64, /system/vendor/lib64]]

It looks like when JNI_Onload is called, it’s called from the java thread. Then, JNI_Onload calls some juce internal things that starts the app by calling MyJuceApp, where things fail. I think it fails because MyJuceApp is initialized from another thread.

So, the class definetly exists because JNI_Onload finds it. But when MyJuceApp constructor is called, it cannot find the same class anymore.

I also tried attaching the thread:

    JNIEnv* env = juce::getEnv();
    JavaVM* vm;
    env->GetJavaVM(&vm);
    JavaVMAttachArgs args;
    args.version = JNI_VERSION_1_6;
    args.name = nullptr;
    args.group = nullptr;
    JNIEnv* env2 = juce::getEnv();
    vm->AttachCurrentThread(&env2, &args);
    auto mainActivityClass1 = env2->FindClass("com/my_app/flutter_app/MainActivity");

and got the same problem.

ps: env->FindClass("java/lang/String") works where env->FindClass("com/my_app/flutter_app/MainActivity") does not, which makes things even more confusing. But the class name is correct, because it’s found on JNI_Onload.

turns out Android does not support finding classes from a different thread.

If you want to find classes inside your juce app, you have to do it on the main thread. However, juce’s JNI_Onload is not exposed from the library, so I don’t know how this could be done.

I had to create my own JNI_Onload and rename juce’s JNI_Onload to JNI_Onload2 and then call JNI_Onload2 from my JNI_Onload after cacheing the classes I need.

This is not good. Any ideas?

Recently I hit this issue, and I came up with a fix. There are two PRs now (now that JUCE accept contributions):

I also wrote the full details in a lengthy blog post here: JUCE on Android: fixing ClassNotFoundException on non-main thread

2 Likes