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

时间:2022-06-26 07:21:38

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库,所以我甚至没有找到一个可以比较的示例项目。

2 个解决方案

#1


12  

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对象的ahold。不幸的是,大多数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应用程序结合在一起的方式。希望这对我以外的人有用。

#2


2  

Here is what I have abstracted from this link as 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调用。这需要一些努力。

#1


12  

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对象的ahold。不幸的是,大多数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应用程序结合在一起的方式。希望这对我以外的人有用。

#2


2  

Here is what I have abstracted from this link as 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调用。这需要一些努力。