来自Android JNI中任何线程的FindClass

Pav*_*l P 43 c++ java-native-interface android

Android的JNI提示页面提到了这个FAQ:为什么FindClass找不到我的课程? 他们提到了多个解决方案,最后一个选项就是这个:

在某个地方缓存对ClassLoader对象的引用,并直接发出loadClass调用.这需要一些努力.

所以,我试图让它工作,似乎无论如何,这种方法根本不适合我.最后,我想到了如何使用ClassLoader但是如果从本机线程尝试未被触摸/加载的loadClass它将无法工作.本质上,当从本机线程调用时,它与行为中的env-> FindClass相同,但是对于已在应用程序中使用的类,它不会返回0.任何想法,如果我没有正确,或者无法从原生线程访问尚未使用/加载的类.






编辑:我会提供更多信息来解释我的意思.有常规的JNI env->FindClass(className),我写的另一个myFindClass(env, className)使用缓存ClassLoader->loadClass.

我试图从本机c/c ++访问的类是"com/noname/TestClient".在myFindClass里面我也使用它返回的env-> FindClass和log值:

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));
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后,我有这三种组合来解释这个问题.

1)

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

//inside native thread created by pthread_create
myFindClass(env, "com/noname/TestClient");
Run Code Online (Sandbox Code Playgroud)

我得到这个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");
Run Code Online (Sandbox Code Playgroud)

我得到这个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");
Run Code Online (Sandbox Code Playgroud)

我得到这个logcat:

myFindClass("com/noname/TestClent")=> c0:0,c1:0,c0和c1相同:1

基本上,我的问题是ClassLoader在第三种情况下找不到我的类.这是一个错误吗?可以采取哪些措施来解决问题?

EDIT2: 最重要的是,似乎ClassLoader :: loadClass显然是错误的.如果我问myFindClass("noname/TestClent")然后它返回一些垃圾,当我以任何方式使用返回的jclass应用程序崩溃.

Áti*_*ves 57

经过多次尝试和崩溃我的应用程序,我和一位同事设法缓存并成功使用另一个本机线程中的类加载器.我们使用的代码如下所示(C++ 11,但很容易转换为C++ 2003),因为我们找不到任何前面提到的示例"在某个地方缓存对ClassLoader对象的引用,并发布loadClass"直接打电话.这需要一些努力." 当从与JNI_OnLoad不同的线程调用时,调用findClass工作得很好.我希望这有帮助.

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 非常好的答案......只是评论,这对我来说不起作用:http://stackoverflow.com/questions/14765776/jni-error-app-bug-accessed-stale-local-reference-0xbc00021-索引-8-在-A-TABL.在gClassLoader对象上调用NewGlobalRef()后,这解决了我的问题.谢谢! (9认同)
  • 谢谢你的解决方案.请注意,在全局存储之前,您必须调用`NewGlobalRef()`.从另一个线程调用`NewGlobalRef()`是行不通的.这样做:`gClassLoader = env-> NewGlobalRef(env-> CallObjectMethod(myClass,getClassLoaderMethod));`有关详细信息,请访问:http://android-developers.blogspot.kr/2011/11/jni-local -reference-变化功能于ics.html (5认同)
  • 当找不到类时,`ClassLoader.loadClass()`不返回null.它抛出异常,这意味着返回值未定义.在调用`CallObjectMethod`之后必须检查异常,如果引发了异常,则不能使用返回值.(这通常是正确的 - 任何`Call*Method`调用都应该跟着'ExceptionCheck`或`ExceptionOccurred`,你可以在日志中看到带有`ExceptionDescribe`的异常.) (4认同)
  • 我明白; 我只是想确保任何复制它的人都将必要的检查添加到`JNI_OnLoad`和`findClass`(特别是后者 - 你可以在那里将异常转换为NULL,并清除异常,以获得你想要的语义).另请注意,您不需要调用`FindClass`来查找`java.lang.Class`; 你可以在你已经拥有的`jclass`上使用`GetObjectClass`(它更快,永远不会在有效对象上失败).你有没有在Android> = 4.0上试过这个?看起来你需要`gFindClassMethod`上的`NewGlobalRef`. (4认同)

Ale*_*tin 6

尝试首先将本机线程附加到JVM.

指向jvm的指针你可以获得第一件事 JNI_OnLoad

env->GetJavaVM(&jvm);
Run Code Online (Sandbox Code Playgroud)

然后从你的本机线程

JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
Run Code Online (Sandbox Code Playgroud)

然后使用envFindClass

  • 亚历克斯,当然我这样做,没有它1)和2)不会工作:) (5认同)