Android 从非UI线程更新视图
我对Android系统的工作原理感到困惑,特别是当它更新视图层次结构时我们都知道,我们不应该从UI(主)线程以外的任何线程更新任何视图。当我们尝试这样做时,甚至安卓系统也会抛出异常。 前几天,我试图在我的应用程序中实现一个自定义进度显示视图。因此,我开始使用标准Java线程和处理程序组合 我的发现让我惊讶,因为我能够从后台线程更新TextViewAndroid 从非UI线程更新视图,android,multithreading,textview,android-view,background-thread,Android,Multithreading,Textview,Android View,Background Thread,我对Android系统的工作原理感到困惑,特别是当它更新视图层次结构时我们都知道,我们不应该从UI(主)线程以外的任何线程更新任何视图。当我们尝试这样做时,甚至安卓系统也会抛出异常。 前几天,我试图在我的应用程序中实现一个自定义进度显示视图。因此,我开始使用标准Java线程和处理程序组合 我的发现让我惊讶,因为我能够从后台线程更新TextView new Thread(new Runnable() { @Override public void run() {
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$CalledFromErrorThreadException:只有
创建视图层次结构的原始线程可以接触其视图
然后,我尝试在sleep()调用前后将setText()调用放入循环100、1000次。当然,该应用程序每次都会崩溃,但我可以在我的textview上看到“睡觉前”文本
所以我的问题是系统何时检测到一些非UI线程试图更新视图。而在非UI线程中没有sleep()调用时,它为什么不工作?线程是UI线程的并行进程。当您尝试将sleep函数放入线程中时,线程的执行将停止。你问题的答案就在问题本身里面。它说-只有创建视图层次结构的原始线程才能接触其视图。另外两个线程运行一个ui线程和您创建的另一个ui线程。当你调用sleep方法时。线程停止与ui线程不同步的执行。当您的线程尝试更改textview的文本时,两个线程都不同步。在睡眠之前,线程是同步的。睡觉后,它们不同步 我用棒棒糖中的
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();
}
我们可以查看TextView
的mLayout
是否为null
,checkForRelayout()
不会被调用,因此应用程序不会崩溃。而mLayout
将在TextView
的onDraw
中初始化。因此,应用程序不会在第一次调用setText
时崩溃,因为mLayout
为空。绘制后,mLayout
被初始化,并导致应用程序在第二次调用setText
时崩溃
我想您应该在绘制TextView
之前(例如在onCreate
或onResume
中)启动线程。对吧?
应用程序是否崩溃取决于您调用setText
的时间。如果在首次绘制TextView
之前调用setText
,则一切正常。否则应用程序将崩溃。只需使用android.os.Handler-它将始终在UI-thread上运行其可运行程序。什么时候?请参见android/view/ViewRootImpl.java
方法名称是checkThread
@Dogcat:我知道Handler。但我的问题是,为什么在第一种情况下系统不会崩溃?我的问题是,为什么系统的行为会像这样,只是有时抛出异常,为什么不是每次都抛出异常。Thread.sleep()是如何改变系统行为的?@pskink:事实上,我从不从非UI线程中接触UI元素,事实上我很少使用线程。我的一位同事(大学新生)做了类似的事情,所以我产生了怀疑。顺便说一句,我对“你不允许那样做”这一点产生了怀疑,因为我能做到。系统运行不一致。所以您的意思是说Thread.sleep()以某种方式初始化TextView中的mLayout。因为即使在1000次循环中调用第一个setText(),它的行为也是一样的。您可以尝试在第一次调用setText
之前添加sleep
。应用程序也会崩溃。在Thread.sleep()
过程中,TextView
已经被绘制,这也意味着mLayout
已经初始化。他可能在做什么。我只是试着睡两秒钟而不是100毫秒,现在我的应用程序也崩溃了。@Dogcat我试着像你的代码一样睡线程100或200毫秒,但我的应用程序还是崩溃了。首先,线程不是进程。先别管sleep()调用,在没有睡眠的情况下尝试代码。代码运行良好。这就是我的疑问所在,为什么android系统会允许任何非UI线程更新视图**它不会抛出异常**。顺便说一下,UI线程和任何其他线程之间都没有同步,不管你是否让它进入睡眠状态。你在开玩笑吗??我已经告诉过你(在你最后的回答中),我知道在为Android编写代码时要使用的所有多线程关注点和相关模式。请重新阅读我的问题。