Android为什么能在子线程中更新UI
刚学习Android开发的时候经常有需要在子线程里更新UI的操作,总是会遇到报错:Only the original thread that created a view hierarchy can touch its views.
“只有创建视图层次结构的原始线程才能触摸其视图。”
因为大部分的View创建都是在UI线程(主线程),所以我们都有一个共识:只有在UI线程中才能够更新UI
那为什么呢?
当我们遇到Only the original thread that created a view hierarchy can touch its views.
的时候,可以看到报错的路径是:at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7905)
说明ViewRootImpl
这个类对当前线程是否为主线程进行了判断:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
ViewRootIml
的checkThread
方法里,通过判断
mThread != Thread.currentThread()
如果当前没有在UI线程,就会抛出异常
那有没有在子线程中可以更新UI的操作呢?
这里放出一段代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.textView);
new Thread(new Runnable() {
@Override
public void run() {
tv.setText("子线程更新UI测试");
}
}).start();
}
这样操作是可以改变TextView的Text属性并且没有报错的。
通过上面的了解可以推断,这个时候没有进入ViewRootImpl
的checkThread
方法,逃过一劫,具体原因我们可以从源码入手。这里贴一下讲的很好的链接。
结论:从源码分析可得知,ViewRootImpl
对象是在onResume
方法回调之后才创建,那么就说明了为什么在生命周期的onCreate
方法里,甚至是onResume
方法里都可以实现子线程更新UI,因为此时还没有创建ViewRootImpl
对象,并不会进行是否为主线程的判断;
总结:
- 在
ViewRootImpl
中有checkThread
方法,如果当前线程不是UI线程会抛出异常 - 子线程可以在ViewRootImpl还没有被创建之前更新UI;
ViewRootImpl
在Activity启动流程中的OnResume()
方法被创建- 访问UI是没有加对象锁的,在子线程环境下更新UI,会造成不可预期的风险;
- 开发者更新UI一定要在主线程进行操作;
归根到底这个问题可以从Android的启动流程来分析,Android启动时,先进行OnCreate
的回调,然后进行OnResume
,这个时候ViewRootImpt
还没有进行创建初始化,所以我们在OnCreate
中在子线程更新UI是可行的。