【问题标题】:Android Can't call Java method from native code JNIAndroid 无法从本机代码 JNI 调用 Java 方法
【发布时间】:2019-09-03 12:55:02
【问题描述】:

我有一个本机代码,我想使用 JNI 从它调用一些 Java 方法,我的主要问题是 JVM envFindClass 方法在我的类的情况下总是返回 null 尽管我写正确但如果有任何 Java像 String 这样的类它返回一个有效的指针,我得到了一个加载 ClassLoader 的示例代码,让它加载我的类,但不幸的是我得到了 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();

有什么帮助吗?

【问题讨论】:

  • 你是说评论前的GetMethodID()调用返回NULL,还是评论后的NewStringUTF()返回NULL?
  • @zenzelezz 你是对的,我编辑了它,它是CallObjectMethod
  • 你怎么知道你得到NULL?请注意,如果出现错误,您没有正确分离当前线程。您如何获得manager.activity-&gt;clazz; 的价值?也发一下。
  • stackoverflow.com/questions/14586821/…。使用NewStringUTF("com.game.Share")
  • 请注意,在分离线程之前必须释放所有本地引用。

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


【解决方案1】:

你被一个已知的Android JNI limitation咬了:

如果您自己创建一个线程(可能通过调用 pthread_create 然后使用 AttachCurrentThread 附加它),您可能会遇到麻烦。现在您的应用程序中没有堆栈帧。如果您从该线程调用 FindClass,JavaVM 将在“系统”类加载器而不是与您的应用程序关联的类加载器中启动,因此尝试查找特定于应用程序的类将失败。

遵循文章中列出的建议应该会让你摆脱困境:

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

【讨论】:

  • void android_main(struct android_app* state) 在幼稚的活动被认为是一个单独的线程?因为当我把代码放在上面时,我也会得到 Null。
  • 是的。但是,您可以从 app-&gt;activity-&gt;clazz 中提取一个工作类加载器并使用选项 3。例如,请参见 this retrieveClass function(将 activity_class 替换为 app-&gt;activity-&gt;clazz,或 manager.activity-&gt;clazz,因为您似乎拥有它)。
猜你喜欢
  • 1970-01-01
  • 2011-08-11
  • 1970-01-01
  • 1970-01-01
  • 2013-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多