Android 无法从本机代码 JNI 调用 Java 方法

Android Can't call Java method from native code JNI

我有一个本机代码,我想使用 JNI 从中调用一些 Java 方法,我的主要问题是 JVM envFindClass 方法总是 returns null在我的 classes 的情况下,虽然我写得正确,但如果任何 Java class 像 String it returns 一个有效的指针,我得到了一个加载 ClassLoader 并让它加载我的 class 但不幸的是我得到了 NULL。 这是代码:

unsigned char* pixels = new unsigned char[3 * width * height];
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
std::string imgURL(dataPath+"/Levels/screenshot.png");
stbi_write_png(imgURL.c_str(), width, height, 3, pixels, width * 3);
stbi_image_free(pixels);

JNIEnv *lJNIEnv;
manager.activity->vm->AttachCurrentThread(&lJNIEnv, NULL);
jobject lNativeActivity =  manager.activity->clazz;
jclass ClassNativeActivity = lJNIEnv->FindClass("android/app/NativeActivity");
jclass contextClass = lJNIEnv->FindClass("android/content/Context");
if(contextClass == 0)
    return;
jmethodID startActivityMethodId = lJNIEnv->GetMethodID(contextClass, "startActivity", "(Landroid/content/Intent;)V");
if(startActivityMethodId == 0)
    return;
jclass intentClass = lJNIEnv->FindClass("android/content/Intent");
if(intentClass == 0)
    return;
jmethodID intentConstructorMethodId = lJNIEnv->GetMethodID(intentClass, "<init>", "()V");
if(intentConstructorMethodId == 0)
    return;
jmethodID intentSetActionMethodId = lJNIEnv->GetMethodID(intentClass, "setAction", "(Ljava/lang/String;)Landroid/content/Intent;");
if(intentSetActionMethodId == 0)
    return;
jmethodID getClassLoader = lJNIEnv->GetMethodID(ClassNativeActivity,"getClassLoader", "()Ljava/lang/ClassLoader;");
if(getClassLoader == 0)
    return;
jobject cls = lJNIEnv->CallObjectMethod(lNativeActivity, getClassLoader);
if(cls == 0)
    return;
jclass classLoader = lJNIEnv->FindClass("java/lang/ClassLoader");
if(classLoader == 0)
    return;
jmethodID findClass = lJNIEnv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
if(findClass == 0)
    return;
jstring intentString = lJNIEnv->NewStringUTF("com/game/Share");
if(intentString == 0)
    return;
jobject intentObject = lJNIEnv->NewObject(intentClass,intentConstructorMethodId);
if(intentObject == 0)
    return;
    //Here I got NULL although the package and class are correct
jclass marketActivityClass = (jclass)lJNIEnv->CallObjectMethod(cls, findClass, intentString);
if(marketActivityClass == 0)
    return;
lJNIEnv->CallVoidMethod(intentObject, intentSetActionMethodId,intentString);
lJNIEnv->CallVoidMethod(lNativeActivity, startActivityMethodId, intentObject);
manager.activity->vm->DetachCurrentThread();

有什么帮助吗?

你被已知的Android JNI limitation咬过:

You can get into trouble if you create a thread yourself (perhaps by calling pthread_create and then attaching it with AttachCurrentThread). Now there are no stack frames from your application. If you call FindClass from this thread, the JavaVM will start in the "system" class loader instead of the one associated with your application, so attempts to find app-specific classes will fail.

遵循文章中列出的建议应该可以让您摆脱困境:

  • 在 JNI_OnLoad 中执行 FindClass 查找一次,并缓存 class 引用供以后使用。作为执行 JNI_OnLoad 的一部分进行的任何 FindClass 调用都将使用与调用 System.loadLibrary 的函数关联的 class 加载器(这是一个特殊规则,用于进行库初始化更方便)。如果您的应用代码正在加载库,FindClass 将使用正确的 class 加载器。
  • 将 class 的实例传递给需要它的函数,方法是声明您的本机方法采用 Class 参数,然后将 Foo.class 传递给函数。
  • 在方便的地方缓存对 ClassLoader 对象的引用,并直接发出 loadClass 调用。这需要一些努力。