如何在Android上用C加载自己的Java类?

12

我正试图使用Android NDK从C语言调用我编写的一些Java代码。该应用程序是NativeActivity应用程序。我必须访问一些仅在Java中可用的功能,而该功能要求你子类化另一个类,因此我不能直接从C语言中调用它们。因此,我有如下的Java代码:

// src/com/example/my/package/SubClass.java
package com.example.my.package;

import android.foo.TheSuperClass;

public class SubClass extends TheSuperClass {
  public SubClass() {
    setImportantProperty(true);
  }
}

我也有类似这样的C代码:

// Some file.c
void setThatImportantJavaProperty() {
  JavaVM *vm = AndroidGetJavaVM(); // This returns a valid JavaVM object
  JNIEnv* env;
  (*vm)->AttachCurrentThread(vm, &env, 0);

  jclass theSubClass = (*env)->FindClass(env, "com/example/my/package/SubClass");
  jthrowable exception = (*env)->ExceptionOccurred(env);
  if (exception) {
    (*env)->ExceptionDescribe(env);
    // This gives me: "java.lang.NoClassDefFoundError: [generic]".
    // Also, theSubClass is null, so the next line causes a segfault.
  }
  jmethodID theSubClassConstructor = (*env)->GetMethodID(env, theSubClass, "<init>", "()V");
  jobject theSubClassObject = (*env)->NewObject(env, theSubClass, theSubClassConstructor);

  (*env)->DeleteLocalRef(env, theSubClass);
  (*env)->DeleteLocalRef(env, theSubClassConstructor);
  (*env)->DeleteLocalRef(env, theSubClassObject);

  (*vm)->DetachCurrentThread(vm);

}

正如内联注释所述,运行此代码会给我一个"java.lang.NoClassDefFoundError: [generic]"错误。当我解压我的apk文件时,它显示了classes.dex文件,其中似乎有我的类。我猜测我错过了关于类路径方面的一些细微之处,但到目前为止我无法解决它。

顺便说一句,在同样的C函数中,我能够毫无问题地调用标准的Android库(例如Log.v),并且可以正确地打印输出。

看起来所有我找到的例子都只展示如何从C中调用标准的Java库,而不是自己编写的Java库,因此我甚至没有找到一个可以进行比较的示例项目。


3
你有没有阅读这篇文章? http://developer.android.com/guide/practices/design/jni.html#faq_FindClass - Klaimmore
是的,我做了。谢谢 - 结果看起来朝着正确的方向发展。一旦我完成它,我会发布完整的解决方案。 - itfische
请也看一下这个链接:http://groups.google.com/group/android-developers/browse_thread/thread/e090b94fe958ab31 - Wei
2个回答

12

我的问题很微妙,这也是为什么Klaimmore和Winston链接的文档不能完全解决问题的原因,因为我正在使用NativeActivity类编写应用程序。这意味着我永远不会有一个可用于本地类加载器的Java堆栈。没有JNI_OnLoad调用,没有Java方法调用我的本地函数,也没有其他我知道的获取本地ClassLoader对象的方法。不幸的是,大部分Android JNI文档都没有考虑到NativeActivity。

然而,这个问题有一个简单的解决方案,可以让你在使用NativeActivity编写应用程序时更容易。只需在Java中对NativeActivity进行子类化即可。这允许你从NativeActivity实例化开始编写和访问任意的Java代码,同时仍然可以像NativeActivity一样使用C完成其他所有操作。它还允许你像文档中描述的那样在C中设置你的JNI调用。做到这一点的代码如下:

package com.example.my.package;

import android.app.NativeActivity;
import android.util.Log;

public class MyNativeActivity extends NativeActivity {
  static {
    System.loadLibrary("my_ndk_lib");  
  }

  private static String TAG = "MyNativeActivity";

  public MyNativeActivity() {
    super();
    Log.v(TAG, "Creating MyNativeActivity");
  }

  public static void MyUsefulJavaFunction() {
    doSomethingAwesome();
  }
}

而在你的 C 库中:

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
  JNIEnv* env;
  if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
    return -1;

  globalMyNativeActivityClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/example/my/package/MyNativeActivity"));

  return JNI_VERSION_1_6;
}

然后在 C 中的某个时刻,你可以执行:

// Some file.c
void doSomethingAwesomeInJava() {
  JavaVM *vm = AndroidGetJavaVM(); // This returns a valid JavaVM object
  JNIEnv* env;
  (*vm)->AttachCurrentThread(vm, &env, 0);

  jmethodID myUsefulJavaFunction = (*env)->GetStaticMethodID(env, globalMyNativeActivityClass, "MyUsefulJavaFunction", "()V");
  (*env)->CallStaticVoidMethod(env, theActivityClass, myUsefulJavaFunction);

  (*env)->DeleteLocalRef(env, myUsefulJavaFunction);

  (*vm)->DetachCurrentThread(vm);
}

这就是我发现的一种将自己新创建的Java类与NativeActivity应用程序结合的方式。希望这对除我以外的其他人也有所帮助。


myUsefulJavaFunction是一个jmethodID。jmethodID不是LocalRef,不应该被删除。 - user207421
这种解决方法在其他情况下也很重要,比如这个! - Alex Cohn
这只是一个有限的解决方法。如果您有多个自定义类怎么办?也许可以从Google文档中提到的JNI_OnLoad获取到那个ClassLoader。 - Cross_
AndroidGetJavaVM() 对我抛出了 Error:(99) undefined reference to AndroidGetJavaVM - C0D3LIC1OU5

2

这是我从链接中摘录的,由Klaimmore提到。

有几种方法可以解决这个问题:

在JNI_OnLoad中进行一次FindClass查找,并缓存类引用以供以后使用。作为执行JNI_OnLoad的一部分进行的任何FindClass调用都将使用与调用System.loadLibrary函数相关联的类加载器(这是一个特殊规则,提供了使库初始化更加方便的功能)。如果您的应用代码正在加载库,则FindClass将使用正确的类加载器。

通过声明本机方法以获取Class参数并传递Foo.class来将类的实例传递给需要它的函数。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接