Как решить "Не найти класс" ... "по пути: DexPathList в моем родном обратном вызове Java - PullRequest
1 голос
/ 12 апреля 2020

Мне не удается перезвонить на Java (Kotlin) из родной части моего Android приложения. Это аудио приложение, поэтому вы увидите слово «Много аудио», но я не думаю, что проблема связана с этим. Я прочитал много статей и экспериментировал с этим несколько дней, но не могу решить свою проблему. Я могу вызвать свой обратный вызов java с моего native_lib.cpp, поэтому я все это проверил, и ссылки на методы между моим JniBridge и моим собственным кодом должны быть правильными. Но я хочу сделать это из отдельного класса, и я также должен иметь возможность делать это из разных потоков и много раз. Поэтому я хочу сохранить свою ссылку на класс обратного вызова JVM как можно более постоянной, но я не понимаю ее правильно.

Мой текущий код не работает с этой ошибкой:

A: java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: JNI GetMethodID called with pending exception java.lang.ClassNotFoundException: Didn't find class "com.my.app.common.jni.JniBridge" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system/vendor/lib64, /system/lib64, /system/vendor/lib64]]
A: java_vm_ext.cc:542]   at java.lang.Class dalvik.system.BaseDexClassLoader.findClass(java.lang.String) (BaseDexClassLoader.java:134)
A: java_vm_ext.cc:542]   at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String, boolean) (ClassLoader.java:379)
A: java_vm_ext.cc:542]   at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String) (ClassLoader.java:312)
A: java_vm_ext.cc:542] 
A: java_vm_ext.cc:542]     in call to GetMethodID
A: java_vm_ext.cc:542] "Thread-6" prio=10 tid=17 Runnable
A: java_vm_ext.cc:542]   | group="main" sCount=0 dsCount=0 flags=0 obj=0x13080000 self=0x7ca400e800
A: java_vm_ext.cc:542]   | sysTid=30175 nice=-16 cgrp=default sched=1073741825/2 handle=0x7c911014f0
A: java_vm_ext.cc:542]   | state=R schedstat=( 5738847 149269 9 ) utm=0 stm=0 core=1 HZ=100
A: java_vm_ext.cc:542]   | stack=0x7c91006000-0x7c91008000 stackSize=1009KB
A: java_vm_ext.cc:542]   | held mutexes= "mutator lock"(shared held)
A: java_vm_ext.cc:542]   native: #00 pc 00000000003cb654  /system/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)+220)

Вот описание того, что я построил:

Я построил этот класс обратного вызова: AudioCallback.h:

#include <jni.h>

class AudioCallback {

public:
   explicit AudioCallback(JavaVM&, jobject&);
    void playBackProgress(int progressPercentage);

private:
    JavaVM& mJvm;
    jobject& mObject;
};

AudioCallback. cpp:

#include <jni.h>>
#include <utils/logging.h>
#include "AudioCallback.h"

AudioCallback::AudioCallback(JavaVM &jvm, jobject &object) : mJvm(jvm), mObject(object) {
}

void AudioCallback::playBackProgress(int progressPercentage) {


    JNIEnv *g_env = NULL;

    int getEnvStat = mJvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
    JavaVMAttachArgs vmAttachArgs;
    vmAttachArgs.version = JNI_VERSION_1_6;
    vmAttachArgs.name = NULL;
    vmAttachArgs.group = NULL;

    if (getEnvStat == JNI_EDETACHED) {
        LOGD("GetEnv: not attached - attaching");
        if (mJvm.AttachCurrentThread(&g_env, &vmAttachArgs) != 0) {
            LOGD("GetEnv: Failed to attach");
        }
    } else if (getEnvStat == JNI_OK) {
        LOGD("GetEnv: JNI_OK");
    } else if (getEnvStat == JNI_EVERSION) {
        LOGD("GetEnv: version not supported");
    }

    if (g_env != NULL) {
        jclass target = g_env->FindClass("com/my/app/common/jni/JniBridge");
        jmethodID id = g_env->GetMethodID(target, "integerCallback", "(I)V");
        g_env->CallVoidMethod(mObject, id, (jint) progressPercentage);
    } else {
        LOGE("JNIEnv is null!");
    mJvm.DetachCurrentThread();
    }
}

В моем native_lib.cpp я получаю JavaVM в JNI_OnLoad следующим образом:

JavaVM *jvm;

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv jvm_env;

    int getEnvStatus = vm->GetEnv((void **) &jvm_env, JNI_VERSION_1_6);

    if (getEnvStatus != JNI_OK) {
        LOGE("JNI_ONLOAD Failed to get the environment using GetEnv()");
        return -1;
    }
    jvm = vm;
    if (jvm == NULL) {
        LOGE("JNI_ONLOAD: globabl jvm is NULL");
    } else {
        LOGD("JNI_ONLOAD: global jvm is NOT NULL");
    }
    LOGD("Onload done");
    return JNI_VERSION_1_6;
}

После этого у меня есть метод, который создает обратный вызов и класс AudioEngine, который его использует:

JNIEXPORT void JNICALL
Java_com_my_app_common_jni_JniBridge_playFromJNI(JNIEnv *env, jobject instance,jstring URI) {

    // Here I instantiate my callback
    callback = std::make_unique<AudioCallback>(*jvm, instance);

    // below code sets up my audio engine class, which calls the AudioCallback during playback.
    // This works without problems.
    const char *uri = env->GetStringUTFChars(URI, NULL);

    AMediaExtractor *extractor = AMediaExtractor_new();
    if (extractor == nullptr) {
        LOGE("Could not obtain AMediaExtractor");
        return;
    }
    media_status_t amresult = AMediaExtractor_setDataSource(extractor, uri);
    if (amresult != AMEDIA_OK) {
        LOGE("Error setting extractor data source, error %d", amresult);
    }
    audioEngine = std::make_unique<AudioEngine>(*extractor, *callback);
    audioEngine->setFileName(uri);
    audioEngine->start();
}

audioEngine использует библиотеку oboe для обработки аудио, поэтому у меня нет никакого контроля над возможным созданием потока там следовательно, AttachCurrentThread() в моем классе обратного вызова. Прикрепление и отсоединение работает, единственное, что не получается, это обратный вызов Java.

1 Ответ

1 голос
/ 20 апреля 2020

Я решил проблему в основном сам (чтение предложения @ Майкла в комментарии и некоторых других местах, упомянутых там, безусловно, помогло!):

Во-первых, я отделил часть "setup" от реальной функции обратного вызова (Это был план с самого начала, но не выполнение его сначала стояло на пути осмотра проблемы).

Во-вторых, в моем native_lib.cpp я создал глобальную ссылку на мой jobject (экземпляр) "объект в playFromJNI из примера в вопросе) перед передачей его в AudioCallback's конструктор, подобный этому:

    myJNIClass = env->NewGlobalRef(instance);
    callback = std::make_unique<AudioCallback>(*g_jvm, myJNIClass);

В-третьих, в реальном методе обратного вызова playbackProgress я обрабатываю только ÀttachToCurrentThread`, если и используйте членов класса, которые я предварительно инициализировал правильными данными.

Вот как выглядит моя реализация AudioCallback:

#include <jni.h>>
#include <utils/logging.h>
#include "AudioCallback.h"


jclass target;
jmethodID id;

AudioCallback::AudioCallback(JavaVM &jvm, jobject object) : g_jvm(jvm), g_object(object) {
    JNIEnv *g_env;
    int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
    LOGD("Env Stat: %d", getEnvStat);

    if (g_env != NULL) {
        target = g_env->GetObjectClass(g_object);
        id = g_env->GetMethodID(target, "integerCallback", "(I)V");
    }
}

void AudioCallback::playBackProgress(int progressPercentage) {
    JNIEnv *g_env;
    int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);

    if (getEnvStat == JNI_EDETACHED) {
        LOGD("GetEnv: not attached - attaching");
        if (g_jvm.AttachCurrentThread(&g_env, NULL) != 0) {
            LOGD("GetEnv: Failed to attach");
        }
    } else if (getEnvStat == JNI_OK) {
        LOGD("GetEnv: JNI_OK");
    } else if (getEnvStat == JNI_EVERSION) {
        LOGD("GetEnv: version not supported");
    }
    g_env->CallVoidMethod(g_object, id, (jint) progressPercentage);
}

Теперь это работает для меня по желанию, но так как я не эксперт по C ++ / JNI, в моем коде все еще могут быть проблемы. Не стесняйтесь указывать на них!

...