来自C线程的更新导致Choreographer跳帧

时间:2013-06-10 08:54:01

标签: android android-ndk java-native-interface ui-thread

我有一个C线程正在发出请求并从服务器接收更新。更新通过JNI调用发送到Java。我的问题发生在我收到一个可以包含多达100个项目的玩家库存时(来自服务器的100个响应,我无法修改此部分)。有时问题会发生,有时不会,但库存越大,我就越有这个问题。

除了以下消息之外,我在logcat中没有任何异常:

06-10 10:09:46.085: I/Choreographer(23815): Skipped 87 frames!  The application may be doing too much work on its main thread.

然后我的应用关闭了。我还需要说,即使我在评论用runOnUiThread更新用户界面的行时,应用程序也会崩溃。

当我使用JNI返回Java但Looper.myLooper() == Looper.getMainLooper()返回false时,我试图检查我是否在UI线程上。

有人遇到过同样的问题吗? C线程是否以某种方式与主线程相关?感谢

修改

当我从服务器收到更新时,会进行以下调用:

  • 从Java线程(不是UI线程):调用名为notifyAll
  • 的C函数
  • notifyAll调用一个名为update的C函数,它将在Java中调用它的等效函数(参见下面的代码)

    void UpdateListenerWrapper::update(Update& u) {
        // Retrieve the current JNIEnv* with the cached JVM
        int status;
        JNIEnv* env;
        bool isAttached = false;
    
        status = gCachedJVM->GetEnv((void **) &env, JNI_VERSION_1_2);
        if(status < 0) {
            __android_log_print(ANDROID_LOG_ERROR, "UpdateListenerWrapper", "Failed to get JNI environment");
            status = gCachedJVM->AttachCurrentThread(&env, NULL);
            if(status < 0) {
                __android_log_print(ANDROID_LOG_ERROR, "UpdateListenerWrapper", "Failed to attach current thread");
                return;
            }
            isAttached = true;
        }
    
        jmethodID update = env->GetMethodID(gClazzUpdateListenerWrapper, "update", "(J)V"); // J stands for Java long type
    
        // Call Java method update from jUpdateListener object
        env->CallVoidMethod(jUpdateListener, update, (jlong)(intptr_t)&u); // Pointer as agument, we'll build the Update object in Java
    
        if (isAttached) {
            gCachedJVM->DetachCurrentThread();
        }
    }
    

我认为问题出在这一行gCachedJVM->GetEnv((void **) &env, JNI_VERSION_1_2);,也许GetEnv会返回UI线程的指针。这可能是问题吗?我该如何解决这个问题?

2 个答案:

答案 0 :(得分:1)

应用程序崩溃与Choreographer投诉无关。这些只是一个警告,表明动画正在挨饿。

您真的希望以显示线程ID的模式查看logcat输出。我从命令行推荐adb logcat -v threadtime。如果您在服务器交互开始时放置一条日志消息,您可以很容易地看到它是否在UI线程上运行(线程ID和进程ID是相同的 - 系统无法保证,但在应用程序中是可靠的)。

永远不要在主线程上进行网络或数据库I / O.如果花费的时间太长,系统会感到无聊,并决定您的应用没有响应。

通过JNI调用本机代码不会将您切换到其他线程。没有C线程和Java线程,只有线程,可以调用和调用用C和Java编写的代码。

回复:问题更新......

GetEnv总是返回指向当前线程数据的指针。此外,CallVoidMethod总是发生在当前线程中;即使你传入错误的JNIEnv它也不会“跳”线程。

GetMethodID调用在具有大量方法的类中可能很昂贵,因此您应该尝试在设置gClassUpdateListenerWrapper的同一点缓存它。从VM中附加和分离线程也可能是昂贵的,并且是最好避免的 - 如果您从Java方法调用此处,那么根据定义它已经附加了。我猜想isAttached永远不会被设置。

这并没有真正解释为什么编舞者会挨饿。我认为你仍然需要向C update()添加一条日志消息并使用logcat -v threadtime来了解哪些线程发生了什么,并使用traceview来查看时间的去向

答案 1 :(得分:0)

我找到了我的问题的解决方案,但它特定于我的应用程序。在update函数(Java端)中,我有一个case没有break,每个更新都触发了新的网络调用(不在UI线程中)。讨厌找到但感谢你的时间和答案,你帮助我解决了这个问题:)