报错信息:
Only the original thread that created a view hierarchy can touch its views.
复制代码
报错信息是在ViewRootImpl.java中出现的
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
复制代码
ViewRootImpl负责DecorView的测量布局绘制的调用,调用DecorView的测量布局绘制后会遍历控件树的测量布局绘制,控件树里面的任何一个小View刷新,最终都会调用到ViewRootImpl的requestLayout方法,最终遍历整个控件树刷新
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
复制代码
scheduleTraversals()函数里是对View进行绘制操作,而在绘制之前都会检查当前线程是否为主线程mThread,如果不是主线程,就抛出异常;这样做法就限制了开发者在子线程中更新UI的操作。
为什么最开始的在onCreate()里子线程对UI的操作没有报错呢,可以设想一下是因为ViewRootImp此时还没有创建,还未进行当前线程的判断。
ViewRootImp 是何时创建的?
从Activity启动过程中寻找,通过分析ActivityThread.java源码,并找到handleResumeActivity方法:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
。。。
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
。。。
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
。。。
}
复制代码
可以看到是在先调用performResumeActivity之后在赋值的
public final ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide) {
if (r != null && !r.activity.mFinished) {
r.activity.performResume();
...
}
}
复制代码
最后发现会回调生命周期的onResume方法。
通过分析可得知,ViewRootImpl对象是在onResume方法回调之后才创建,那么就说明了为什么在生命周期的onCreate方法里,甚至是onResume方法里都可以实现子线程更新UI,因为此时还没有创建ViewRootImpl对象,并不会进行是否为主线程的判断
总结
必须要在主线程更新UI,实际是为了提高界面的效率和安全性,带来更好的流畅性;
你反推一下,假如允许多线程更新UI,但是访问UI是没有加锁的,一旦多线程抢占了资源,那么界面将会乱套更新了,体验效果就不言而喻了;
所以在Android中规定必须在主线程更新UI