关于Android子线程更新UI问题
2017-11-05 本文已影响111人
chocolatezhu
昨晚任玉刚老师在时光课堂有一场直播秀,里面提到了这么一个问题:
在OnCreate里启动一个子线程更新UI为什么程序没有抛出异常?
这个问题之前也有了解过,但这一提起来我似乎又忘记了为什么,所以记录一下。
代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTxt = (TextView) findViewById(R.id.txt);
new Thread(new Runnable() {
@Override
public void run() {
mTxt.setText("UI Test!");
}
}).start();
按照开发规范来说,是不建议在子线程更新UI的,这必然会引起程序闪退,但是上面在OnCreate里启动子线程更新UI却没有问题,也不抛异常。好,那我们让子线程休眠200ms再更改UI。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTxt = (TextView) findViewById(R.id.txt);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
mTxt.setText("UI Test!");
}
}).start();
}
这时候你会发现程序起不来了,并且控制台里打印出了我们非常熟悉的异常信息
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7680)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1223)
简单解释一下,更新UI的时候系统会去检查当前更新UI的线程是否属于主线程,而该检查操作是在ViewRootImpl.checkThread()中完成的,因为在OnCreate的时候ViewRootImpl还没有被创建,所以这时候就无法检查该更新UI的线程是否属于主线程,自然程序不会报错。休眠200ms后,程序会报错是因为这时候ViewRootImpl已经初始化完成了(ViewRootImpl是在OnResume回调后完成创建的),这时候再去更新UI,自然就会报错。
为什么Android的设计子线程不能更新UI呢,这自然是有一定道理的。UI是用户和程序交互的窗口,任何涉及到UI的操作更新都是要及时响应的,如果多个线程可以同时访问UI的话,这肯定是不安全的,加个线程锁可以不可以呢?可以,但是这太影响效率了,所以Google的工程师这么设计没毛病。
如果要更加了解这个问题,那就需要跟进源码去分析了,这里就不展开了。