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

itf*_*che 10 c java java-native-interface android android-ndk

我试图调用我使用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);
  }
}
Run Code Online (Sandbox Code Playgroud)

我也有这样的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);

}
Run Code Online (Sandbox Code Playgroud)

正如内联注释所说,运行它会给我一个"java.lang.NoClassDefFoundError:[generic]"错误.当我解压缩我的apk时,它会显示classes.dex文件,它似乎包含了我的类.我的猜测是有一些关于类路径的遗漏,但到目前为止我无法解决它.

顺便说一句,我能够从C中对标准的Android库进行类似的调用而没有问题(在上面的相同C函数中).具体来说,我测试了调用Log.v并且正常工作并打印输出.

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

itf*_*che 12

我的问题的微妙之处,以及为什么由Klaimmore和Winston链接的文档并没有完全解决问题,这源于我使用NativeActivity类编写应用程序的事实.这意味着永远不会有一个带有本地类加载器的Java堆栈.没有JNI_OnLoad调用,没有Java方法调用我的本机函数,并且没有其他方法(我知道)获取本地ClassLoader对象的ahold.不幸的是,大多数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();
  }
}
Run Code Online (Sandbox Code Playgroud)

在你的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;
}
Run Code Online (Sandbox Code Playgroud)

然后在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);
}
Run Code Online (Sandbox Code Playgroud)

这就是我发现将自己的新Java类与NativeActivity应用程序结合在一起的方式.希望这对我以外的人有用.


Wei*_*Wei 2

这是我从克莱莫尔提到的这个链接中摘录的内容。

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

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

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

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