LINUX.ORG.RU

Не могу найти (FindClass) свой java класс из native кода

 , , ,


0

1

Сделал launcher activity на java, и успешно запускаю интентом нативную activity - пример glfm (треугольник), ок. Теперь хочу передать в нативную activity некие параметры (начальные сдвиги треугольника, float), которые объявлены static-классом внутри лончера (см. ниже). Пытаюсь до них добраться из натива, пишу в glfm/src/glfm_platform_android.c, android_main() (наверно неправильное место, временно пока):

...
(*vm)->AttachCurrentThread...
...
LOG_DEBUG("dbg-1");
jclass contextClass = (*jni)->FindClass(jni,
  "com/the1/the1app/The1AppLauncherActivity");
if (_glfmWasJavaExceptionThrown()) {
  LOG_DEBUG("dbg-1a");
}
...

Кидается исключение (logcat пишет оба dbg выхлопа). (да, конечно, мне нужно добавить "$The1AppSettings", я убрал для простоты)

Лончер app/src/main/java/com/the1/the1app/The1AppLauncherActivity.java выглядит так:

package com.the1.the1app;
...
public class The1AppLauncherActivity extends Activity {
...
    public static class The1AppSettings {
        public static float dx = 0.5f;
...
Я наверняка что-то простое упускаю. Почему не могу найти свой java лончер? Подскажите пожалуйста.


Вот вывод (*jni)->ExceptionDescribe(jni);:

01-08 17:50:31.853  6362  6413 I GLFM    : dbg-1
01-08 17:50:31.854  6362  6413 W System.err: java.lang.ClassNotFoundException: Didn't find class "com.the1.the1app.The1AppLauncherActivity" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib, /vendor/lib, /system/lib, /vendor/lib]]
01-08 17:50:31.854  6362  6413 W System.err:    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
01-08 17:50:31.854  6362  6413 W System.err:    at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
01-08 17:50:31.854  6362  6413 W System.err:    at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
01-08 17:50:31.854  6362  6413 I GLFM    : dbg-1a
Гуглёж пока ничего не дал. Обычно советуют какую-то чёрную магию или гуёвые настройки (я в командной строке компилю).

the1 ()
Ответ на: комментарий от Legioner

Спасибо (не читал). Возможно в этом проблема. Однако, я попробовал, залез внутрь android_native_app_glue.c, вписал аналогичный вызов в начало android_app_create(), где ещё точно UI-thread. Ошибка чуть другая:

01-08 23:20:24.094  2698  2698 I threaded_app: g-dbg-1
01-08 23:20:24.094  2698  2698 W System.err: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
01-08 23:20:24.094  2698  2698 I threaded_app: g-dbg-1a
(дальше тот же выхлоп что и раньше)

ProGuard-а нет.

the1 ()

А что если тупо записать данные из лончера в файл и послать запускающий интент (это в джаве), а потом (так же тупо) прочитать данные из файла (в си)? Такой тупой подход мне грозит какими-то проблемами?

the1 ()
Ответ на: комментарий от the1

Я тебе (или кому-то другому) в прошлом треде давал ведь ссылку на примеры.

Сделай Proxy-класс с native-методами, сгенерируй с помощью javah хедер, который подцепляй к своему нативному коду ну и вызывай из Java нужные тебе Native-методы на здоровье. Передавай свои параметры в аргументах или вообще массивом.

Ты уверен, что тебе нужно именно из C/C++-кода в Java-код стучаться? Наоборот обычно делают в твоём случае. Как я понял ты обвязываешь Native activity и хочешь менять его параметры из вне. Какая же это обвязка, когда не Java-стучится внутрь нативной либы, а нативная либа стучиться внутрь Java?

EXL ★★★★★ ()
Ответ на: комментарий от the1

Что-то жуткое ты сейчас написал.

Вообще-то ты с помощью Intent и можешь передавать данные между Activity, для этого в Intent имеются методы putExtra() и getExtra().

Первая ссылка в Google: https://startandroid.ru/ru/uroki/vse-uroki-spiskom/67-urok-28-extras-peredaem-dannye-s-pomoschju-intent.html

В любом случае, тебе эти параметры всё равно в твою нативную либу нужно будет перекинуть с помощью JNI и тех же Native-методов.

EXL ★★★★★ ()
Ответ на: комментарий от EXL

Спасибо.

Я тебе (или кому-то другому) в прошлом треде давал ведь ссылку на примеры.

Ты про этот пост (Gish и Spout)?

Как я понял ты обвязываешь Native activity и хочешь менять его параметры из вне.

Да.

Какая же это обвязка, когда не Java-стучится внутрь нативной либы, а нативная либа стучиться внутрь Java?

Не понял «не Java-стучится». Я хотел сделать передачу параметров по образу и подобию твоего Гиша.

Можно ещё проконсультироваться? Я правильно понимаю что есть 2 способа написания нативных аппов:

  • Использовать android_native_app_glue. Даёт отдельный thread, что плюс, и минус — геморрой как сейчас. (моему аппу кмк не нужен, так что вроде минус). Основанная на этом методе glfm создаёт готовый контекст, и обрабатывает тач.
  • Создать обычную java активити, подгрузив свой нативную либу, и дёргая нативные методы. Создание контекста и тач — обрабатываешь сам в джаве.

1-ый метод более кросс-платформенней. Ключевой код аппа (который пишу я; меня не интересуют андроидо-специфика glfm, я от неё изолировался) — удастся весь сделать в C++. Весь, кроме передачи параметров туда-сюда.

Твои примеры (Gish,Spout) — это второй метод. Его минус - более андроидо (джава) специфичен.

Так? Правильно понимаю?

Что-то жуткое ты сейчас написал.

А почему? Кмк, через файлик — это очень хорошо, потому что кросс-платформенней. По мне — так первый способ + параметры в файлике — хороший вариант.

В любом случае, тебе эти параметры всё равно в твою нативную либу нужно будет перекинуть с помощью JNI и тех же Native-методов.

Причём если я выбираю 1-ый способ, у меня будет ровно эта же проблема что я описал в ОП?

Спасибо за консультации!

the1 ()
Ответ на: комментарий от the1

Ты про этот пост (Gish и Spout)? Не понял «не Java-стучится». Я хотел сделать передачу параметров по образу и подобию твоего Гиша.

Ага. Я про Spout говорил. Там тот ещё говнокод, конечно, но быстро разберёшься что к чему, потому что кода не так много и завязки на SDL2 нет.

Так? Правильно понимаю?

Да, android_native_app_glue предполагает то, что тебе практически ничего не нужно из внешнего Java-мира. То есть это вариант для того, чтобы писать тупо на C++ в Native Activity и не касаться Java вообще. Если тебе это важно, то – отлично. Насчёт кроссплатформенности я не совсем понял. Что с android_native_app_glue, что с Java-обёрткой которая стучится в SO’шку – твоё приложение будет кроссплатформенным. Разве что во втором случае ты сможешь не так сильно пачкать C/C++-код условной компиляцией (ifdef’ами).

А почему? Кмк, через файлик — это очень хорошо, потому что кросс-платформенней. По мне — так первый способ + параметры в файлике — хороший вариант.

Ну если тебя устраивает подобное, то я могу предложить компромиссное и много где используемое решение – переменные окружения. В Java-коде своего Activity устанавливаешь переменные окружения, а потом из C++ с помощью обычного getenv() забираешь их. См. как пример https://stackoverflow.com/a/20437164

EXL ★★★★★ ()

Немного углубился в вашу проблему, описанную в стартовом посте.

Причём если я выбираю 1-ый способ, у меня будет ровно эта же проблема что я описал в ОП?

Нет. При передаче параметров через Intent’ы у вас никаких проблем быть не должно, поскольку они прикрепляются (?) именно к вашему экземпляру класса NativeActivity. Например, делаете такой класс:

public class LauncherActivity extends Activity {

    public static class Settings {
        public static String  MY_OPTION_1 = "String from Java";
        public static float   MY_OPTION_2 = 398.0f;
        public static boolean MY_OPTION_3 = false;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.launcher);

        Button runButton = (Button)findViewById(R.id.button);
        runButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(v.getContext(), android.app.NativeActivity.class);

                intent.putExtra("MY_OPTION_1", Settings.MY_OPTION_1);
                intent.putExtra("MY_OPTION_2", Settings.MY_OPTION_2);
                intent.putExtra("MY_OPTION_3", Settings.MY_OPTION_3);

                startActivity(intent);
            }
        });
    }
}

Потом где-нибудь в нативном коде приложения, например, в вашей обёртке к GLFM, в функции onSurfaceCreated() пишете такую JNI-лапшу:

#ifdef __ANDROID_NDK__
    JNIEnv *jni;

    JavaVM *vm = FILE_COMPAT_ANDROID_ACTIVITY->vm;
    (*vm)->AttachCurrentThread(vm, &jni, NULL);

    jobject objektNA = FILE_COMPAT_ANDROID_ACTIVITY->clazz;
    jclass clazzNA = (*jni)->GetObjectClass(jni, objektNA);
    jmethodID metidNA_Intent = (*jni)->GetMethodID(jni, clazzNA, "getIntent", "()Landroid/content/Intent;");

    jobject objektI = (*jni)->CallObjectMethod(jni, objektNA, metidNA_Intent);
    jclass clazzI = (*jni)->GetObjectClass(jni, objektI);
    jmethodID metidI_String = (*jni)->GetMethodID(jni, clazzI, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;");
    jmethodID metidI_Float = (*jni)->GetMethodID(jni, clazzI, "getFloatExtra", "(Ljava/lang/String;F)F");
    jmethodID metidI_Boolean = (*jni)->GetMethodID(jni, clazzI, "getBooleanExtra", "(Ljava/lang/String;Z)Z");

    jstring paramiS_Name = (*jni)->NewStringUTF(jni, "MY_OPTION_1");
    jstring paramiF_Name = (*jni)->NewStringUTF(jni, "MY_OPTION_2");
    jstring paramiZ_Name = (*jni)->NewStringUTF(jni, "MY_OPTION_3");

    jstring paramiS = (*jni)->CallObjectMethod(jni, objektI, metidI_String, paramiS_Name);
    const char *paramiS_RAW = (*jni)->GetStringUTFChars(jni, paramiS, NULL);
    jfloat paramiF = (*jni)->CallFloatMethod(jni, objektI, metidI_Float, paramiF_Name);
    jboolean paramiZ = (*jni)->CallBooleanMethod(jni, objektI, metidI_Boolean, paramiZ_Name);

    printf("===> JNI INTENT PARAMS: %s | %f | %s", paramiS_RAW, paramiF, paramiZ ? "true" : "false");

    (*jni)->ReleaseStringUTFChars(jni, paramiS, paramiS_RAW);
    (*jni)->ReleaseStringUTFChars(jni, paramiZ_Name, NULL);
    (*jni)->ReleaseStringUTFChars(jni, paramiF_Name, NULL);
    (*jni)->ReleaseStringUTFChars(jni, paramiS_Name, NULL);
    (*jni)->DeleteLocalRef(jni, clazzI);
    (*jni)->DeleteLocalRef(jni, objektI);
    (*jni)->DeleteLocalRef(jni, clazzNA);
    (*jni)->DeleteLocalRef(jni, objektNA); // ???????????
#endif

Дебажите, хватаетесь за голову и понимаете, что через setenv()/getenv() сделать подобное куда как проще :D

См. старый проект, который я обновил и в котором немного подправил структуру:
https://github.com/EXLMOTODEV/GLFM-example

Параметры передаются из Java с помощью Intent’а и потом забираются из нативного кода с помощью JNI и просто пишутся в лог. С выводом их на экран не стал заморачиваться, ибо лень искать либы, которые умеют рисовать текст в контексте OpenGL ES.

Удачи!

EXL ★★★★★ ()
Ответ на: комментарий от EXL

Спасибо!

Ну, поскольку вы не прокомментировали почему файлик — жуть, я предварительно решил делать именно так. Мне правда видится это очень правильным. Да, при этом я не пишу под андроид как следует (= не интегрирую апп в систему), но в этом моём случае это всё не нужно. Аппик простой, и пусть будет так.

Соответственно мне немного неудобно что вы всё это сделали. Посмотрю только потом, нужен ли вызов AttachCurrentThread (там ведь один не-UI поток, и AttachCurrentThread уже был в нём вызван).

И я как-то привык «на ты», кмк так лучше.

the1 ()
Ответ на: комментарий от EXL

Причём если я выбираю 1-ый способ, у меня будет ровно эта же проблема что я описал в ОП?

Нет. При передаче параметров через Intent’ы у вас никаких проблем быть не должно, поскольку они прикрепляются (?) именно к вашему экземпляру класса NativeActivity

Да, я это не подумав спросил. jni-то работает, просто класс не находит. Глупость, а вы время поратили. Извиняюсь.

the1 ()
Ответ на: комментарий от the1

Ну, поскольку вы не прокомментировали почему файлик — жуть, я предварительно решил делать именно так.

Я думал это просто очевидно. Представьте, если бы ваше десктопное приложение зависело от двух библиотек. И вместо обычных вызовов функций с аргументами, первая библиотека писала бы в файл, а вторая из него читала.

В пределах одного приложения подобное видится достаточно странным. Ну и про накладные расходы на файловую систему на практике забывать тоже не стоит. Передача аргументов через JNI или переменные окружения будет куда как быстрее.

EXL ★★★★★ ()