无法执行 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."