android — Невозможно выполнить JavaVM-> DetachCurrentThread (): & quot; попытка отсоединения, пока выполняется код & quot;

У меня есть Android-приложение, которое использует NDK — обычное Java-приложение для Android с обычным пользовательским интерфейсом и ядром C ++. В ядре есть места, где мне нужно вызывать методы Java, а это значит, что мне нужно JNIEnv* для этой темы, что в свою очередь означает, что мне нужно позвонить JavaVM->AttachCurrentThread() чтобы получить действительный env,

Раньше просто делал AttachCurrentThread и не удосужился отделиться вообще. В Dalvik все работало нормально, но ART прерывает приложение, как только вызывает поток AttachCurrentThread выход без звонка DetachCurrentThread, Итак, я прочитал ссылку JNI, и на самом деле он говорит, что я должен позвонить DetachCurrentThread, Но когда я это делаю, ART прерывает приложение со следующим сообщением:

пытаясь отсоединиться, пока еще выполняется код

В чем тут проблема, и как позвонить DetachCurrentThread должным образом?

4

Решение

Dalvik также прервет работу, если нить выйдет без отсоединения. Это реализуется через ключ pthread — см. threadExitCheck() в Thread.cpp.

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

Второй и последующие вызовы присоединения, как определено в спецификации, являются недорогими бездействующими операциями. Подсчета ссылок нет, поэтому detach всегда отсоединяется, независимо от того, сколько было присоединений. Одним из решений является добавление собственной обертки с подсчетом ссылок.

Другой подход — прикреплять и отсоединять каждый раз. Это используется каркасом приложения при определенных обратных вызовах. Это был не столько преднамеренный выбор, сколько побочный эффект от оборачивания исходных текстов Java вокруг кода, разработанного в первую очередь на C ++, и попытки обуздать функциональность. Если вы посмотрите на SurfaceTexture.cpp, в частности JNISurfaceTextureContext::onFrameAvailable()Вы можете видеть, что когда SurfaceTexture необходимо вызвать функцию обратного вызова на языке Java, он присоединит поток, вызовет обратный вызов, а затем, если поток только что был присоединен, немедленно отсоединит его. Флаг «needsDetach» устанавливается путем вызова GetEnv чтобы увидеть, была ли нить ранее присоединена.

Это не очень важно с точки зрения производительности, так как каждое присоединение должно выделять объект Thread и выполнять некоторые внутренние операции с виртуальными машинами, но это приводит к правильному поведению.

8

Другие решения

Я попробую прямой и практичный подход (с примером кода, без использования классов), чтобы ответить на этот вопрос для случайного разработчика, который обнаружил эту ошибку в Android, в тех случаях, когда она работала и после обновления ОС или фреймворка ( Qt?) Это начало создавать проблемы с этой ошибкой и сообщением.

    JNIEXPORT void Java_com_package_class_function(JNIEnv* env.... {

JavaVM* jvm;
env->GetJavaVM(&jvm);

JNIEnv* myNewEnv; // as the code to run might be in a different thread (connections to signals for example) we will have a 'new one'
JavaVMAttachArgs jvmArgs;
jvmArgs.version = JNI_VERSION_1_6;

int attachedHere = 0; // know if detaching at the end is necessary
jint res = jvm->GetEnv((void**)&myNewEnv, JNI_VERSION_1_6); // checks if current env needs attaching or it is already attached
if (JNI_EDETACHED == res) {
// Supported but not attached yet, needs to call AttachCurrentThread
res = jvm->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&myNewEnv), &jvmArgs);
if (JNI_OK == res) {
attachedHere = 1;
} else {
// Failed to attach, cancel
return;
}
} else if (JNI_OK == res) {
// Current thread already attached, do not attach 'again' (just to save the attachedHere flag)
// We make sure to keep attachedHere = 0
} else {
// JNI_EVERSION, specified version is not supported cancel this..
return;
}

// Execute code using myNewEnv
// ...

if (attachedHere) { // Key check
jvm->DetachCurrentThread(); // Done only when attachment was done here
}
}

Все обретало смысл, увидев Документы API Invocation для GetEnv:

ВОЗВРАТ:
Если текущий поток не подключен к виртуальной машине, устанавливает * env в NULL и возвращает JNI_EDETACHED. Если указанная версия не поддерживается, устанавливает * env в NULL и возвращает JNI_EVERSION. В противном случае устанавливает * env на соответствующий интерфейс и возвращает JNI_OK.

Кредиты для:
— Этот вопрос Получение ошибки "пытаясь отсоединиться, пока еще выполняется код" при вызове JavaVm->DetachCurrentThread это в его примере прояснило, что необходимо дважды проверять каждый раз (даже если перед вызовом detach это не делается).
— @Michael, что в комментариях к этому вопросу он четко отмечает, что не звонил по телефону.
— То, что @fadden сказал: «Там нет подсчета ссылок, поэтому отсоединение всегда отсоединяется, независимо от того, сколько было присоединений».

2