无法执行 JavaVM->DetachCurrentThread():"attempting to detach while still running code"
Can't execute JavaVM->DetachCurrentThread(): "attempting to detach while still running code"
我有一个使用 NDK 的 Android 应用程序 - 具有常规 UI 和 C++ 核心的常规 Android Java 应用程序。在核心中有些地方我需要调用 Java 方法,这意味着我需要为该线程调用 JNIEnv*
,这反过来意味着我需要调用 JavaVM->AttachCurrentThread()
来获取有效env
。
以前,只是在做 AttachCurrentThread
,根本懒得分离。它在 Dalvik 中运行良好,但一旦调用 AttachCurrentThread
的线程退出而不调用 DetachCurrentThread
,ART 就会中止应用程序。所以我阅读了 JNI 参考资料,确实它说我必须调用 DetachCurrentThread
。但是当我这样做时,ART 会中止应用程序并显示以下消息:
attempting to detach while still running code
这里有什么问题,如何正确调用DetachCurrentThread
?
如果线程在没有分离的情况下退出,Dalvik 也会中止。这是通过 pthread 键实现的——参见 Thread.cpp 中的 threadExitCheck()
。
除非调用堆栈为空,否则线程不能分离。这背后的原因是为了确保像监视器锁(即 synchronized
语句)这样的任何资源在堆栈展开时被正确释放。
按照规范的定义,第二次和后续的附加调用是低成本的空操作。没有引用计数,所以分离总是分离,不管发生了多少次附加。一种解决方案是添加您自己的引用计数包装器。
另一种方法是每次附加和分离。这由应用程序框架在某些回调中使用。这与其说是一个有意的选择,不如说是将 Java 源代码包装在主要用 C++ 开发的代码周围,并试图硬塞其中的功能。如果你看看 SurfaceTexture.cpp,尤其是JNISurfaceTextureContext::onFrameAvailable()
,可以看到当SurfaceTexture需要调用Java语言的回调函数时,它会附加线程,调用回调,然后如果线程刚刚附加它会立即分离它。 "needsDetach" 标志是通过调用 GetEnv
来设置的,以查看线程之前是否已附加。
这在性能方面不是一件好事,因为每个附加都需要分配一个 Thread 对象并执行一些内部 VM 内务处理,但它确实产生了正确的行为。
我将尝试一种直接实用的方法(使用示例代码,不使用 类)为偶尔在 android 中遇到此错误的开发人员回答这个问题,以防万一他们在哪里工作,在 OS 或框架更新(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
}
}
看到 GetEnv 的 The Invocation API docs 后一切都明白了:
RETURNS:
If the current thread is not attached to the VM, sets *env to NULL, and returns JNI_EDETACHED. If the specified version is not supported, sets *env to NULL, and returns JNI_EVERSION. Otherwise, sets *env to the appropriate interface, and returns JNI_OK.
感谢:
- 这个问题 Getting error "attempting to detach while still running code" when calling JavaVm->DetachCurrentThread 在其示例中清楚地表明每次都必须仔细检查(即使在调用 detach 之前它并没有这样做)。
- @Michael 在这个问题的评论中,他清楚地指出了不调用分离。
- @fadden 说的:"There's no reference counting, so detach always detaches, no matter how many attaches have happened."
我有一个使用 NDK 的 Android 应用程序 - 具有常规 UI 和 C++ 核心的常规 Android Java 应用程序。在核心中有些地方我需要调用 Java 方法,这意味着我需要为该线程调用 JNIEnv*
,这反过来意味着我需要调用 JavaVM->AttachCurrentThread()
来获取有效env
。
以前,只是在做 AttachCurrentThread
,根本懒得分离。它在 Dalvik 中运行良好,但一旦调用 AttachCurrentThread
的线程退出而不调用 DetachCurrentThread
,ART 就会中止应用程序。所以我阅读了 JNI 参考资料,确实它说我必须调用 DetachCurrentThread
。但是当我这样做时,ART 会中止应用程序并显示以下消息:
attempting to detach while still running code
这里有什么问题,如何正确调用DetachCurrentThread
?
如果线程在没有分离的情况下退出,Dalvik 也会中止。这是通过 pthread 键实现的——参见 Thread.cpp 中的 threadExitCheck()
。
除非调用堆栈为空,否则线程不能分离。这背后的原因是为了确保像监视器锁(即 synchronized
语句)这样的任何资源在堆栈展开时被正确释放。
按照规范的定义,第二次和后续的附加调用是低成本的空操作。没有引用计数,所以分离总是分离,不管发生了多少次附加。一种解决方案是添加您自己的引用计数包装器。
另一种方法是每次附加和分离。这由应用程序框架在某些回调中使用。这与其说是一个有意的选择,不如说是将 Java 源代码包装在主要用 C++ 开发的代码周围,并试图硬塞其中的功能。如果你看看 SurfaceTexture.cpp,尤其是JNISurfaceTextureContext::onFrameAvailable()
,可以看到当SurfaceTexture需要调用Java语言的回调函数时,它会附加线程,调用回调,然后如果线程刚刚附加它会立即分离它。 "needsDetach" 标志是通过调用 GetEnv
来设置的,以查看线程之前是否已附加。
这在性能方面不是一件好事,因为每个附加都需要分配一个 Thread 对象并执行一些内部 VM 内务处理,但它确实产生了正确的行为。
我将尝试一种直接实用的方法(使用示例代码,不使用 类)为偶尔在 android 中遇到此错误的开发人员回答这个问题,以防万一他们在哪里工作,在 OS 或框架更新(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
}
}
看到 GetEnv 的 The Invocation API docs 后一切都明白了:
RETURNS: If the current thread is not attached to the VM, sets *env to NULL, and returns JNI_EDETACHED. If the specified version is not supported, sets *env to NULL, and returns JNI_EVERSION. Otherwise, sets *env to the appropriate interface, and returns JNI_OK.
感谢: - 这个问题 Getting error "attempting to detach while still running code" when calling JavaVm->DetachCurrentThread 在其示例中清楚地表明每次都必须仔细检查(即使在调用 detach 之前它并没有这样做)。 - @Michael 在这个问题的评论中,他清楚地指出了不调用分离。 - @fadden 说的:"There's no reference counting, so detach always detaches, no matter how many attaches have happened."