問題
我們都知道Android在子線程中更新UI會報錯:
Only the original thread that created a view hierarchy can touch its views。
但是,現(xiàn)在有如下代碼,卻可以正常運行。
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的繪制肯定是發(fā)生在activity聲明周期onResume之后的,但具體是在哪個階段就要從源碼中找了。
ActivityThread
onCreate、onStart、onResume既然是個方法,肯定是從某個管理Activity聲明周期執(zhí)行的類中被調(diào)用的,這個類就是ActivityThread。
handleLaunchActivity
-
#performLaunchActivity通過反射創(chuàng)建Activity
-
activity.attachmWindow = 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進(jìn)而調(diào)用到onCreate- 然后
setContentView會初始化decorView,以及我們的視圖放到decorView下構(gòu)成View樹
- 然后
-
handleStartActivity
-
activity.performStart("handleStartActivity")mInstrumentation.callActivityOnStart(this)
handleResumeActivity
-
#performResumeActivityactivity.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的第一次requestLayoutscheduleTraversalsmChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //將在下一次Looper輪循的時候調(diào)用mTraversalRunnable#run()
view.assignParent(this);這里的view是decor,this是
ViewRootImpl,也就是將decor的parent設(shè)置成了ViewRootImpl。到這里為止,上至
ViewRootImpl,到decor,再到咱們自己寫的View,這一顆view樹就算構(gòu)建完成了。
-
-
-
-
ViewRootImpl下的TraversalRunnable
TraversalRunnable是ViewRootImpl下的子類,所以以下這些方法都是寫在ViewRootImpl下的。所以就像Activity的生命周期相關(guān)方法是在
ActivityThread中被調(diào)用一樣,View的繪制流程相關(guān)方法被調(diào)用的源頭就是ViewRootImpl。
-
run()->doTraversal()->performTraversals()performMeasureperformLayoutperformDraw
這三個方法就對應(yīng)了View下的onMeasure,onLayout ,onDraw,所以會從ViewRootImpl開始,至上而下的開始繪制流程。
解答
再回到最開始的那個問題,再onCreate中開辟線程,竟然可以成功更新UI。
會一層一層往上執(zhí)行requestLayout。
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
假設(shè)這時更新到decorView了,再去往上找ViewRootImpl,因為ViewRootImpl是在onResume之后root.setView()的時候才被設(shè)置為decor的parent,所以這個時候decor是沒有parent的,所以自然會略過這行代碼,直接開始更新了。
如果為這個線程加上延時,使其在view.assignParent(this)執(zhí)行完之后,再調(diào)用requestLayout,就會執(zhí)行到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”的異常了。