【问题标题】:FindClass from any thread in Android JNI来自 Android JNI 中任何线程的 FindClass
【发布时间】:2012-11-07 04:56:51
【问题描述】:

Android 的 JNI 提示页面提到了这个FAQ: Why didn't FindClass find my class? 他们提到了多种解决方案,最后一个选项是这个:

在方便的地方缓存对 ClassLoader 对象的引用,然后发出 loadClass 直接调用。这需要一些努力。

所以,我试图让它工作,但似乎无论如何,这种方法对我来说根本不起作用。最终,我想出了如何使用 ClassLoader 但如果我从本机线程尝试加载尚未触摸/加载的类,它将无法工作。本质上,它在从本机线程调用时的行为与 env->FindClass 相同,只是它不会为应用程序中已使用的类返回 0。任何想法,如果我没有做对,或者不可能从尚未使用/加载的本机线程访问类。






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

我试图从本机 c/c++ 访问的类是“com/noname/TestClient”。在 myFindClass 中,我还使用 env->FindClass 并记录它返回的值:

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

那么,我有这 3 个组合来解释这个问题。

1)

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

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

我得到了这个 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");

我得到了这个 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");

我得到了这个 logcat:

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

基本上,我的问题是 ClassLoader 在第三种情况下找不到我的课程。它是一个错误吗?有什么办法可以解决这个问题?

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

【问题讨论】:

  • 是的,这很正常,Android 应用默认不使用系统类加载器。只需将您需要的所有内容缓存在 JNI_OnLoad() 中即可。
  • Re: EDIT2: 听起来该方法抛出了异常,此时返回值未定义。 loadClass() 从不返回 null;它要么返回类引用,要么抛出异常。
  • @fadden 可能是这样。我不记得到底是什么问题,但我最终解决了。

标签: android c++ java-native-interface


【解决方案1】:

在我的应用程序多次尝试和崩溃之后,我和一位同事设法缓存并成功地在另一个本机线程中使用类加载器。我们使用的代码如下所示(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;
}

【讨论】:

  • ClassLoader.loadClass() 在找不到类时不返回 null。它抛出一个异常,这意味着返回值是未定义的。调用CallObjectMethod 后必须检查异常,如果引发异常,则不能使用返回值。 (这通常是正确的——任何Call*Method 调用都应该跟在ExceptionCheckExceptionOccurred 之后,您可以在日志中使用ExceptionDescribe 看到异常。)
  • 我明白了;我只是想确保任何复制它的人都向JNI_OnLoadfindClass 添加了必要的检查(尤其是后者——你可以在那里将异常转换为NULL,并清除异常,以获得你想要的语义)。另请注意,您无需致电FindClass 即可找到java.lang.Class;您可以在已有的jclass 上使用GetObjectClass(它更快,并且在有效对象上永远不会失败)。您是否在 Android >= 4.0 上尝试过这个?看起来您需要在 gFindClassMethod 上使用 NewGlobalRef
  • 酷。 FWIW,我的意思是 gClassLoader——gFindClassMethod 是一个 jmethodID。
  • 优秀的答案...只是一个评论,这对我没有用,因为:stackoverflow.com/questions/14765776/…。在 gClassLoader 对象上调用 NewGlobalRef() 后,这解决了我的问题。谢谢!
  • 感谢您的解决方案。请注意,在全局存储之前,您必须致电 NewGlobalRef()。从不同的线程调用NewGlobalRef() 是行不通的。这样做:gClassLoader = env-&gt;NewGlobalRef(env-&gt;CallObjectMethod(myClass, getClassLoaderMethod)); 更多详情请点击此处:android-developers.blogspot.kr/2011/11/…
【解决方案2】:

首先尝试将您的本机线程附加到 JVM。

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

env->GetJavaVM(&jvm);

然后从你的本机线程

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

然后将env 用于FindClass

【讨论】:

  • Alex,当然我会这样做,没有它 1) 和 2) 将无法工作:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多