Как загрузить свой собственный класс Java в C на Android? - PullRequest
10 голосов
/ 27 июля 2011

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

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

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

Ответы [ 2 ]

12 голосов
/ 03 августа 2011

Тонкость моего вопроса и то, почему документация, на которую ссылаются Klaimmore и Winston, не совсем решают проблему, связана с тем, что я пишу приложение с использованием класса NativeActivity. Это означает, что у меня никогда не будет стека 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 голосов
/ 28 июля 2011

Вот что я абстрагировал от этой ссылки , как упомянуто Клаиммором.

Есть несколько способов обойти это:

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

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

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

...