【问题标题】:How do I load my own Java class in C on Android?如何在 Android 上的 C 中加载我自己的 Java 类?
【发布时间】:2011-10-13 21:10:33
【问题描述】:

我正在尝试调用我使用 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);

}

正如内联 cmets 所说,运行它会给我一个“java.lang.NoClassDefFoundError: [generic]”错误。当我解压缩我的 apk 时,它显示了 classes.dex 文件,其中似乎包含我的课程。我的猜测是,我在类路径方面缺少一些微妙之处,但到目前为止我无法解决它。

顺便说一句,我可以毫无问题地从 C 对标准 Android 库进行类似的调用(在上面的同一个 C 函数中)。具体来说,我测试了调用 Log.v,它可以正常工作并正确打印输出。

我发现的所有示例似乎都只显示了如何从 C 调用普通 Java 库,而不是您自己编写的 Java 库,所以我什至没有找到可以比较的示例项目。

【问题讨论】:

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


【解决方案1】:

这是我从 Klaimmore 提到的 link 中提取的内容。

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

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

将类的实例传递给需要它的函数,方法是声明您的本机方法接受 Class 参数,然后传入 Foo.class。

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

【讨论】:

    【解决方案2】:

    我的问题的微妙之处,以及为什么 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,不应删除。
    • 此解决方法在其他情况下也很重要,例如 this one!
    • 这是一个有限的解决方法。如果您有多个自定义类怎么办?也许有可能从谷歌文档中提到的 JNI_OnLoad 获得那个 ClassLoader。
    • AndroidGetJavaVM() 为我抛出 Error:(99) undefined reference to AndroidGetJavaVM
    猜你喜欢
    • 1970-01-01
    • 2012-01-31
    • 2014-03-23
    • 1970-01-01
    • 1970-01-01
    • 2016-05-04
    • 2012-04-29
    • 1970-01-01
    • 2015-01-12
    相关资源
    最近更新 更多