4

In android I use pthread_create to create a native thread , then in the Callback procedure ,call FindClass to get a Java class. but it does'nt work. I get tips from android jni tips I found the solution in FindClass from any thread in Android JNI

I modify it for my project like this [edit]

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);
    gClassLoader = env->NewGlobalRef(gClassLoader);
    gFindClassMethod = env->GetMethodID(classLoaderClass, "loadClass",
                                    "(Ljava/lang/String;)Ljava/lang/Class;");

    //check. this is ok
    jclass cls = env->FindClass("com/example/data/DataTest");
    jmethodID methoID = env->GetMethodID(cls, "name", "()Ljava/lang/String;");
    LOG_INFO("cls is %p\n", cls);

    return JNI_VERSION_1_6;
}

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


jclass findClass(const char* name) {
     JNIEnv *env = getEnv();
     jclass resultClass = 0;
     if(env)
     {
        resultClass = env->FindClass(name);
        //it can not found class in native thread, use loadClass method
        if (!resultClass)
        {
            LOG_INFO("can not find the class");
            //return value is not null. 
            return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name))); 
        }
    }
    return resultClass;
}
.......
//thread callback 
void *proc(void *)
{
  JNIEnv *env = getEnv();
  jclass cls = findClass("com/example/data/DataTest");
  if (cls)
  {
    LOG_INFO("GetMethodID");
    //crash
    jmethodID methodID = env->GetMethodID(cls, "name", "()Ljava/lang/String;"); 
    LOG_INFO("proc tag is %p\n", tag);
  }
}
.....
pthread_create(&handle, NULL, proc, 0);
.....

program is exit at env->GetMethodID. I get this error:

Invalid indirect reference 0x40d8bb20 in decodeIndirectRef.

if i remove resultClass = env->FindClass(name); from findClass, it's OK. "proc tag is " can be printed.

//correct 
jclass findClass(const char* name) {
     JNIEnv *env = getEnv();
     jclass resultClass = 0;
     if(env)
     {
        if (!resultClass)
        {
            return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name))); 
        }
    }
    return resultClass;
}

any confilct between env->FindClass(name); and env->CallObjectMethod to loadClass?

Is it a bug? What can be done to fix the problem?

Community
  • 1
  • 1
mario_qiu
  • 105
  • 1
  • 10
  • 1
    Is the JNIEnv* pointer acquired properly with success? Can you also post the code for the getEnv() function? Is the error produced by the FindClass() or the CallObjectMethod()-call? – Mark Vincze Sep 11 '14 at 08:15
  • @mario_qiu Oh please. You can see for yourself that code in comments is totally illegible. Edit it into your post. – user207421 Sep 11 '14 at 08:29
  • It's missing from the code how and when `gJvm` is acquired, that can matter too. – Mark Vincze Sep 11 '14 at 08:32
  • And does your code step in the `if (!resultClass)` branch in `findClass`? – Mark Vincze Sep 11 '14 at 08:55
  • yes. in native thread that use pthread_create function created, env->FindClass return null – mario_qiu Sep 11 '14 at 08:59
  • It could be a problem that `gClassLoder` is just a local reference and not a global one. Try creating a global reference to it in `OnLoad`: gClassLoader = env->NewGlobalRef(gClassLoader); – Mark Vincze Sep 11 '14 at 09:00
  • I don't think `FindClass` should return nullptr if you pass it a correct string. Can you double-check that the `DataTest` class exists and it's in the correct package? – Mark Vincze Sep 11 '14 at 09:03
  • @Mark Vincze path is correct. i check it in JNI_OnLoad. – mario_qiu Sep 11 '14 at 09:11
  • If you try the same FindClass call in OnLoad, does it fail there too? – Mark Vincze Sep 11 '14 at 11:33
  • @Mark Vincze of course not. It is excuted in main thread. But not in the native thread which created by pthread_create function. – mario_qiu Sep 11 '14 at 14:31
  • Can you show the full "JNI WARNING" message from logcat? What version of Android are you using? – fadden Sep 12 '14 at 06:14

3 Answers3

2

Don't do this:

gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);

In particular, never take a local reference (which is what CallObjectMethod returns) and store it in anything but a local variable.

You need to acquire a global reference, using NewGlobalRef, if you want to access that value outside the function that acquired the local reference. Once execution returns to the VM in that thread, the local reference is invalidated.

See the "local and global references" section in the JNI Tips document.

fadden
  • 51,356
  • 5
  • 116
  • 166
2

Sorry, I make a silly mistake.

resultClass = env->FindClass(name);

env->FindClass(name) throw an NoClassDefFoundException and return NULL. Application continues on. VM abort in jmethodID methodID = env->GetMethodID(cls, "name", "()Ljava/lang/String;"); coincidentally . May be one solution is:

jclass findClass(const char* name) {
     JNIEnv *env = getEnv();
     jclass resultClass = 0;
     if(env)
     {
        resultClass = env->FindClass(name);
        jthrowable mException = env->ExceptionOccurred();
        if (mException )
        {
            env->ExceptionDescribe();
            env->ExceptionClear();
            return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name))); 
        }
    }
    return resultClass;
}

I found some helpful usage in http://android.wooyd.org/JNIExample Thanks!

mario_qiu
  • 105
  • 1
  • 10
1

I use a bit different code to get the actual JNIEnv* pointer (it is based on a code sample posted here on SO somewhere). This approach proved to be working reliably.

class BaseJNI
{
protected:
    BaseJNI(JNIEnv * env)
    {
        int ret = env->GetJavaVM(&jvm);
        if( ret != JNI_OK )
        {
            LOG_INFO("Could not get JavaVM: %d", ret);
            throw;
        }
    }

    JNIEnv * GetEnv()
    {
        JNIEnv * env;
        // double check it's all ok
        int ret = jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
        if (ret == JNI_OK)
            return env;

        if (ret == JNI_EDETACHED)
        {
            LOG_INFO("GetEnv: thread is not attached, trying to attach");
            ret = jvm->AttachCurrentThread(&env, nullptr);
            if( ret == JNI_OK )
            {
                LOG_INFO("GetEnv: attach successful");
                return env;
            }

            LOG_INFO("Cannot attach JNI to current thread: %d", ret);
            return nullptr;
        }

        LOG_INFO("could not get JNI ENV: %d", ret);
        return nullptr;
    }

protected:
    JavaVM *jvm;
};

This class should be instantiated in your main thread when you surely have a valid JNIEnv* pointer, then it can be used from background threads as well.

Could you try to get the JNIEnv* pointer with the above code and see if the pointer is acquired properly?

Mark Vincze
  • 7,737
  • 8
  • 42
  • 81
  • log in JNI_OnLoad and proc `JNI_OnLoad Main Thread env is 0x2446d8` `in proc env is 0x3fb150` – mario_qiu Sep 11 '14 at 08:33
  • Umm, so getting the `JNIEnv*` pointer works, but `FindClass` still crashes? Are you absolutely sure that it's the `FindClass` call and not something else? – Mark Vincze Sep 11 '14 at 08:34
  • jmethodID methodid = env->GetMethodID(cls, "name", "()Ljava/lang/String;"); crashes. – mario_qiu Sep 11 '14 at 08:40