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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6838397/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 17:30:49  来源:igfitidea点击:

How do I load my own Java class in C on Android?

javaandroidcjava-native-interfaceandroid-ndk

提问by itfische

I am trying to call some Java code that I wrote from C using the Android NDK. The application is a NativeActivity application. I have to access some functionality that is only available in Java, and the functionality requires you to subclass another class, so I can't just directly do the calls from C. Thus, I have Java code like this:

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

I also have C code like this:

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

}

As the inline comments say, running this gives me a "java.lang.NoClassDefFoundError: [generic]" error. When I unpack my apk, it shows me the classes.dex file, which appears to have my class in it. My guess is that there is some subtlety that I am missing regarding classpaths, but I am unable to resolve it thus far.

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

Incidentally, I am able to make similar calls to standard Android libraries from C without a problem (in the same C function above). Specifically, I tested calling Log.v and that works and prints the output correctly.

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

It seems that all the examples that I am finding only show how to call normal Java libraries from C, not Java libraries you have written yourself, so I haven't even found an example project to compare against.

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

回答by itfische

The subtlety of my question, and why the documentation linked to by Klaimmore and Winston does not quite resolve the issue, stems from the fact that I am writing an app using the NativeActivity class. This means that there is never a Java stack with a local class loader available to me. There is no JNI_OnLoad call, there is no Java method calling into my native function, and there is no other way (that I am aware of) of getting ahold of a local ClassLoader object. Unfortunately, most of the Android JNI documentation is simply not written with NativeActivity in mind.

我的问题的微妙之处,以及为什么 Klaimmore 和 Winston 链接的文档不能完全解决问题,源于我正在使用 NativeActivity 类编写应用程序这一事实。这意味着从来没有一个带有本地类加载器的 Java 堆栈可供我使用。没有 JNI_OnLoad 调用,没有调用我的本机函数的 Java 方法,并且没有其他方法(我知道)获取本地 ClassLoader 对象。不幸的是,大多数 Android JNI 文档根本没有考虑到 NativeActivity。

However, there is a straightforward solution to this problem that can make your life much easier when writing apps using NativeActivity. Simply subclass NativeActivity in Java. This allows you to write and access arbitrary Java code from the beginning of your NativeActivity's instantiation, while still doing everything else in C as NativeActivity allows. It also allows you to set up your JNI calls from C in the manner described in those documents. Doing this looks something like the following:

然而,这个问题有一个简单的解决方案,可以让你在使用 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();
  }
}

And in your C library:

在你的 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;
}

Then at some point in C, you can do:

然后在 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);
}

And that is the way I found to incorporate my own new Java classes with a NativeActivity app. Hopefully this will be useful to someone besides me.

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

回答by Wei

Here is what I have abstracted from this linkas mentioned by Klaimmore.

这是我从Klaimmore 提到的这个链接中提取的内容。

There are a few ways to work around this:

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

Do your FindClass lookups once, in JNI_OnLoad, and cache the class references for later use. Any FindClass calls made as part of executing JNI_OnLoad will use the class loader associated with the function that called System.loadLibrary (this is a special rule, provided to make library initialization more convenient). If your app code is loading the library, FindClass will use the correct class loader.*

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

Pass an instance of the class into the functions that need it, by declaring your native method to take a Class argument and then passing Foo.class in.

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

Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.

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