Подтвердить что ты не робот

Как загрузить собственный Java-класс в C на Android?

Я пытаюсь вызвать некоторый код Java, который я написал на C, используя Android NDK. Приложение является приложением 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, который, как представляется, имеет мой класс. Я предполагаю, что есть какая-то тонкость, которую я пропускаю относительно classpaths, но пока не могу ее разрешить.

Кстати, я могу сделать подобные звонки в стандартные Android-библиотеки с C без проблем (в той же функции C выше). В частности, я протестировал вызов Log.v, и это работает и правильно печатает вывод.

Кажется, что все примеры, которые я нахожу, показывают только, как вызывать обычные библиотеки Java из C, а не библиотеки Java, которые вы написали сами, поэтому я даже не нашел примерный проект для сравнения.

4b9b3361

Ответ 1

Тонкость моего вопроса и почему документация, связанная с Klaimmore и Winston, не совсем решает проблему, проистекает из того факта, что я пишу приложение, используя класс NativeActivity. Это означает, что для Java никогда не существует Java-стек с локальным загрузчиком классов. Нет вызова JNI_OnLoad, в моей нативной функции нет метода Java, и нет другого способа (который я знаю) получить локальный локальный объект ClassLoader. К сожалению, большая часть документации JNI для Android просто не написана с учетом NativeActivity.

Однако есть прямое решение этой проблемы, которое может сделать вашу жизнь намного проще при написании приложений с использованием NativeActivity. Просто подкласс NativeActivity в Java. Это позволяет вам писать и получать доступ к произвольному Java-коду с самого начала создания экземпляра NativeActivity, но все еще делает все остальное на C, как позволяет NativeActivity. Он также позволяет вам настроить вызовы JNI с C способом, описанным в этих документах. Выполнение этого выглядит примерно так:

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. Надеюсь, это будет полезно для меня, кроме меня.

Ответ 2

Вот что я отвлекся от этой ссылки, как упоминалось в Klaimmore.

Существует несколько способов обойти это:

Выполняйте поиск в FindClass один раз в JNI_OnLoad и кешируйте ссылки на классы для дальнейшего использования. Любые вызовы FindClass, выполненные как часть выполнения JNI_OnLoad, будут использовать загрузчик классов, связанный с функцией, которая называется System.loadLibrary(это специальное правило, обеспечивающее удобство инициализации библиотеки). Если ваш код приложения загружает библиотеку, FindClass будет использовать правильный загрузчик классов. *

Передайте экземпляр класса в нужные ему функции, объявив свой собственный метод принять аргумент класса, а затем передав Foo.class.

Кэшировать ссылку на объект ClassLoader где-то удобно, и вызывать вызовы loadClass напрямую. Это требует определенных усилий.