【问题标题】:Updating Views from non-UI threads从非 UI 线程更新视图
【发布时间】:2015-12-07 11:44:34
【问题描述】:

我对 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 次。当然,该应用程序每次都会崩溃,但我能够在我的文本视图上看到“睡前”文本。

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

【问题讨论】:

  • 只需使用 android.os.Handler - 它总是会在 UI 线程上运行它的可运行对象。
  • 什么时候?见android/view/ViewRootImpl.java 方法名是checkThread
  • @Dogcat :我知道 Handler。但我的问题是,为什么在第一种情况下系统不会崩溃?
  • 我的问题是,为什么系统会这样,只是有时抛出异常,为什么不是每次。 Thread.sleep() 如何改变系统行为?
  • @pskink :实际上我从不接触非 UI 线程的 UI 元素,事实上我很少使用线程。我的一位同事(大学新生)做了这样的事情,所以我想到了这个疑问。顺便说一句,我的怀疑就在“你不能这样做”这一点上,因为我能够做到。系统行为不一致。

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


【解决方案1】:

我在 Lollipop 中使用sleep 运行您的代码 sn-p,但它崩溃了。堆栈跟踪是:

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();
}

我们可以看到TextView的mLayout是null,checkForRelayout()不会被调用,因此应用程序不会崩溃。而mLayout 将在TextView 的onDraw 中初始化。所以应用程序在第一次调用setText 时不会崩溃,因为mLayout 为空。绘制后,mLayout 被初始化,并导致应用程序在第二次调用 setText 时崩溃。

我猜你在绘制TextView 之前启动Thread(例如在onCreate 或onResume 中)。对吧?

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

【讨论】:

  • 所以你的意思是说 Thread.sleep() 以某种方式初始化了 TextView 中的 mLayout。因为即使你在 1000 次循环中调用第一个 setText(),它的行为也是一样的。
  • 您可以尝试在第一次调用setText 之前添加sleep。应用也会崩溃。
  • 在 Thread.sleep() 期间,TextView 已经被绘制,这也意味着 mLayout 已初始化。
  • 他可能在做某事。我刚尝试睡两秒钟而不是 100 毫秒,现在我的应用程序也崩溃了。
  • @Dogcat 我尝试像在您的代码中一样将线程休眠 100 或 200 毫秒,但我的应用程序仍然崩溃。
【解决方案2】:

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

【讨论】:

  • 首先线程不是进程。忘记 sleep() 调用一分钟,尝试不睡眠的代码。代码工作正常。这就是我怀疑的地方,为什么android系统会允许任何非UI线程更新视图。 **它不会抛出异常**。顺便说一下,UI线程和任何其他线程之间没有同步,无论你是否让它进入睡眠状态。
  • 你在开玩笑吗??我已经告诉过你(在你最后的回答中),我知道在为 Android 编写代码时要使用的所有多线程问题和相关模式。 请重新阅读我的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-24
  • 1970-01-01
相关资源
最近更新 更多