问题
我们都知道Android在子线程中更新UI会报错:
Only the original thread that created a view hierarchy can touch its views
。
但是,现在有如下代码,却可以正常运行。
private ImageView mImageView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView)findViewById(R.id.iv);
new Thread(new Runnable() {
@Override
public void run() {
mImageView.setImageResource(R.drawable.ic_book);//更新 ui
}
}).start();
}
所以就从这个问题入手,回顾下Android中的绘制流程。
声明周期和绘制流程
我们知道Activity的声明周期是onCreate->onStart->onResume
,View的绘制流程是 onMeasure->onLayout ->onDraw
,而且,我们在activity的声明周期的这三个阶段是获取不到View的宽高信息的,也就是说View的绘制肯定是发生在activity声明周期onResume
之后的,但具体是在哪个阶段就要从源码中找了。
ActivityThread
onCreate
、onStart
、onResume
既然是个方法,肯定是从某个管理Activity声明周期执行的类中被调用的,这个类就是ActivityThread
。
handleLaunchActivity
-
#performLaunchActivity
通过反射创建Activity
-
activity.attach
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); mWindowManager = mWindow.getWindowManager();
-
mInstrumentation.callActivityOnCreate
-
activity.performCreate
进而调用到onCreate
- 然后
setContentView
会初始化decorView
,以及我们的视图放到decorView
下构成View树
- 然后
-
handleStartActivity
-
activity.performStart("handleStartActivity")
mInstrumentation.callActivityOnStart(this)
handleResumeActivity
-
#performResumeActivity
activity.performResume
decor.setVisibility(View.INVISIBLE);
wm = a.getWindowManager()
是一个WindowManagerImpl
-
wm.addView(decor, l)
-
WindowManagerImpl#addView()
-
WindowManagerGlobal#addView
-
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); root.setView(view, wparams, panelParentView, userId);
-
requestLayout()//view的第一次requestLayout
scheduleTraversals
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //将在下一次Looper轮循的时候调用mTraversalRunnable#run()
view.assignParent(this);
这里的view是decor,this是
ViewRootImpl
,也就是将decor的parent设置成了ViewRootImpl
。到这里为止,上至
ViewRootImpl
,到decor
,再到咱们自己写的View
,这一颗view树就算构建完成了。
-
-
-
-
ViewRootImpl下的TraversalRunnable
TraversalRunnable
是ViewRootImpl
下的子类,所以以下这些方法都是写在ViewRootImpl
下的。所以就像Activity的生命周期相关方法是在
ActivityThread
中被调用一样,View的绘制流程相关方法被调用的源头就是ViewRootImpl
。
-
run()
->doTraversal()
->performTraversals()
performMeasure
performLayout
performDraw
这三个方法就对应了View下的onMeasure,onLayout ,onDraw
,所以会从ViewRootImpl
开始,至上而下的开始绘制流程。
解答
再回到最开始的那个问题,再onCreate中开辟线程,竟然可以成功更新UI。
会一层一层往上执行requestLayout
。
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
假设这时更新到decorView
了,再去往上找ViewRootImpl
,因为ViewRootImpl
是在onResume
之后root.setView()
的时候才被设置为decor的parent,所以这个时候decor是没有parent的,所以自然会略过这行代码,直接开始更新了。
如果为这个线程加上延时,使其在view.assignParent(this)
执行完之后,再调用requestLayout
,就会执行到ViewRootImpl#requestLayout
方法中来:
@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.");
}
}
这个时候,如果线程不对的话,就会报“子线程不能更新UI”的异常了。