无法执行JavaVM-> DetachCurrentThread():“在仍然运行代码时尝试分离”

时间:2015-01-13 14:02:31

标签: android c++ android-ndk java-native-interface dalvik

我有一个使用NDK的Android应用程序 - 一个具有常规UI和C ++核心的常规Android Java应用程序。我需要在核心中调用Java方法,这意味着我需要JNIEnv*该线程,这反过来意味着我需要调用JavaVM->AttachCurrentThread()来获取有效的env }。

以前,只是做AttachCurrentThread并且根本没有分离。它在Dalvik中运行良好,但只要调用AttachCurrentThread的线程退出而不调用DetachCurrentThread,ART就会中止应用程序。所以我已经阅读了JNI参考资料,事实上它说我必须致电DetachCurrentThread。但是,当我这样做时,ART会使用以下消息中止该应用程序:

  

尝试在仍然运行代码时分离

这里的问题是什么,以及如何恰当地致电DetachCurrentThread

2 个答案:

答案 0 :(得分:8)

如果线程退出而没有分离,Dalvik也会中止。这是通过pthread键实现的 - 请参阅Thread.cpp中的threadExitCheck()

除非调用堆栈为空,否则线程可能不会分离。这背后的原因是确保在堆栈展开时正确释放监视器锁(即synchronized语句)等任何资源。

根据规范的定义,第二次和后续的附加调用是低成本的无操作。没有引用计数,所以无论发生了多少附件,分离总是分离。一种解决方案是添加自己的引用计数包装器。

另一种方法是每次都附加和分离。应用程序框架在某些回调中使用它。这不是一个有意识的选择,而是围绕主要用C ++开发的代码包装Java源代码的副作用,并尝试在功能方面崭露头角。如果你看一下SurfaceTexture.cpp,特别是{{ 1}},你可以看到当SurfaceTexture需要调用Java语言回调函数时,它将附加线程,调用回调,然后如果刚刚附加了线程,它将立即将其分离。 "需要Detach"通过调用JNISurfaceTextureContext::onFrameAvailable()来设置标志,以查看该线程之前是否已附加。

这在性能方面并不是一件好事,因为每个附件都需要分配一个Thread对象并执行一些内部VM内务处理,但它确实会产生正确的行为。

答案 1 :(得分:3)

我将尝试一种直接且实用的方法(使用示例代码,而不使用类)为偶尔在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
        }
    }

看到GetEnv的The Invocation API docs之后,一切都变得有意义了:

  

退货:   如果当前线程未连接到VM,则将* env设置为NULL,并返回JNI_EDETACHED。如果不支持指定的版本,请将* env设置为NULL,并返回JNI_EVERSION。否则,将* env设置为适当的接口,并返回JNI_OK。

信用至: -这个问题Getting error "attempting to detach while still running code" when calling JavaVm->DetachCurrentThread在其示例中明确表明,有必要每次都进行仔细检查(即使在调用detach之前并没有这样做)。 -@Michael在此问题中的评论他清楚地指出了有关不调用detach的内容。 -@fadden说的是:“没有引用计数,因此无论发生了多少附件,分离总是分离。”