从非UI线程更新视图

时间:2015-12-07 11:44:34

标签: android multithreading textview android-view background-thread

我对Android系统的工作原理感到困惑,特别是当它更新视图层次结构时。 我们都知道不应该从UI(主)线程以外的任何线程更新任何视图。当我们尝试这样做时,甚至 Android系统会抛出异常。 前几天我试图在我的应用程序中实现显示视图的自定义进度。所以我开始使用标准的Java线程和处理程序组合。

我发现让我感到惊讶,因为我能够从后台线程更新TextView。

new Thread(new Runnable() {

        @Override
        public void run() {
            mTextView.setText("I am " + Thread.currentThread().getName());
        }
    }).start();

之后我尝试更新其他视图,效果非常好。所以我尝试在后台线程中进行睡眠调用。

new Thread(new Runnable() {

        @Override
        public void run() {
            mTextView.setText("Thread : before sleep");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mTextView.setText("Thread : after sleep");
        }
    }).start();

它如预期的那样崩溃

  

android.view.ViewRootImpl $ CalledFromWrongThreadException:只有   创建视图层次结构的原始线程可以触及其视图。

然后我尝试在sleep()调用之前和之后将循环中的setText()调用放置100次,1000次。当然,应用程序每次都崩溃,但我能够在textview上看到“Before Sleep”文本。

所以我的问题是 系统何时检测到某些非UI线程正在尝试更新视图 。并且 为什么在非UI线程中没有sleep()调用时它不起作用

2 个答案:

答案 0 :(得分:2)

我在Lollipop中使用sleep运行您的代码段,然后崩溃了。堆栈跟踪是:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
        at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:909)
        at android.view.ViewGroup.invalidateChild(ViewGroup.java:4690)
        at android.view.View.invalidateInternal(View.java:11801)
        at android.view.View.invalidate(View.java:11765)
        at android.view.View.invalidate(View.java:11749)
        at android.widget.TextView.checkForRelayout(TextView.java:6850)
        at android.widget.TextView.setText(TextView.java:4057)
        at android.widget.TextView.setText(TextView.java:3915)
        at android.widget.TextView.setText(TextView.java:3890)
        at com.test.MainActivity$16.run(MainActivity.java:1126)
        at java.lang.Thread.run(Thread.java:818)

因此密钥隐藏在TextView.setText的第4057行附近:

if (mLayout != null) {
    checkForRelayout();
}

我们可以看到mLayout的{​​{1}}是TextViewnull是否会被调用,因此应用不会崩溃。 checkForRelayout()将在mLayout的{​​{1}}中进行初始化。因此,第一次调用onDraw时应用程序不会崩溃,因为TextView为空。绘制后,setText已初始化,导致应用在第二次调用mLayout时崩溃。

我猜您在mLayout被绘制之前开始setText(例如在ThreadTextView中)。正确?

应用程序是否崩溃取决于您拨打的时间onCreate。如果您在首次绘制onResume之前致电setText,一切正常。否则应用程序崩溃。

答案 1 :(得分:-1)

Thread是UI线程的并行进程。当您尝试将sleep函数放入线程时,线程的执行会停止。你问题的答案就在问题本身内部。它说 - 只有创建视图层次结构的原始线程才能触及其视图。所以其他的是两个线程运行一个ui线程,另一个是你创建的。当你调用睡眠方法时。你的线程停止执行与ui线程没有同步。当你的线程试图改变textview的文本时,线程都不同步。在睡眠之前,线程是同步的。睡觉后他们不同步。