之前零散寫了一些宪拥,這篇算是總集篇。大概涉及到
- setContentView()
- SubDecor铣减、Decorview她君、ContentView
- PhoneWindow、WindowManager
- ViewRootImpl葫哗、Choreographer
- Toast.show()缔刹、Dialog.show()
- View.invalidate()、View.requestLayout()
- View.post()
新寫一個(gè)Activity會(huì)調(diào)用到setContentView()
方法設(shè)置布局劣针,那就以此作為切入點(diǎn)校镐。
Activity.setContentView()
->AppCompatDelegateImpl.setContentView()
public void setContentView(int resId) {
//創(chuàng)建SubDecor
ensureSubDecor();
//id為content的FrameLayout
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//移除content上所有view
contentParent.removeAllViews();
//加載xml布局到content
LayoutInflater.from(mContext).inflate(resId, contentParent);
//回調(diào)
mOriginalWindowCallback.onContentChanged();
}
創(chuàng)建SubDecor這個(gè)ViewGroup(SubDecor包含ActionBar、Toolbar酿秸、ContentView等)灭翔,并將SubDecor添加到window的Decorview,Decorview就是頂層View了辣苏;找到SubDecor上id為content的FrameLayout肝箱,加載xml布局反射創(chuàng)建View樹并添加到content。
ensureSubDecor()
內(nèi)部調(diào)用createSubDecor()
稀蟋,createSubDecor()中調(diào)用mWindow.setContentView(subDecor)將subDecor添加到Window的Decorview煌张。
Window大家都知道,其實(shí)現(xiàn)類是PhoneWindow
退客,在Activity.attach()方法中初始化骏融。而Activity.attach()在ActivityThread的performLaunchActivity()方法中調(diào)用,performLaunchActivity()中創(chuàng)建了Activity實(shí)例萌狂,加載資源档玻,調(diào)用activity.attach()然后通過Instrumentation類回調(diào)onCreate()生命周期。當(dāng)然這篇也不是分析Activity啟動(dòng)流程之類的文章茫藏,只需關(guān)注PhoneWindow误趴。
Activity.attach()
final void attach(...){
mWindow = new PhoneWindow(this, window, activityConfigCallback);
}
PhoneWindow.setContentView()
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
}
mContentParent.addView(view, params);
}
//mContentParent
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
//Window.findViewById(int id)
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
創(chuàng)建DecorView,將SubDecor添加到DecorView务傲。installDecor()方法中可以看到mContentParent是DecorView中id為content的ViewGroup凉当,那這玩意是平常我們所說的ContentView嗎?其實(shí)不是售葡,剛看到這里我也有點(diǎn)懵逼看杭。如果這玩意是ContentView,那SubDecor已經(jīng)添加進(jìn)了ContentView挟伙,View樹再add進(jìn)來豈不是會(huì)覆蓋SubDecor中的Toolbar楼雹、Actionbar?實(shí)際上ContentView是SubDecor的子View,繼續(xù)看下去烘豹。
AppCompatDelegateImpl.createSubDecor()
隨便挑一個(gè)創(chuàng)建SubDecor的分支
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
精簡(jiǎn)一下很明顯了瓜贾。DecorView中id為content的windowContentView重置為NO_ID
,SubDecor中id為action_bar_activity_content的contentView設(shè)置為android.R.id.content
携悯;并將原DecorView中windowContentView下的子View剪切到SubDecor的ContentView祭芦。
abc_screen_toolbar.xml
<androidx.appcompat.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include layout="@layout/abc_screen_content_include"/>
...
</androidx.appcompat.widget.ActionBarOverlayLayout>
abc_screen_content_include.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.widget.ContentFrameLayout
android:id="@id/action_bar_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</merge>
所以SubDecor是ActionBarOverlayLayout,ContentView是SubDecor中的ContentFrameLayout
setContentView()看完了憔鬼,View樹何時(shí)開始繪制真正顯示出來呢龟劲?老生常談的是onResume()之后,那就從onResume()生命周期的調(diào)用處開始看轴或。
ActivityThread.handleResumeActivity()
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
//onResume回調(diào)
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
wm.addView(decor, l);
r.activity.makeVisible();
}
Activity.makeVisible()
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
熟悉的WindowManager.addView()昌跌。WindowManager的實(shí)現(xiàn)類是WindowManagerImpl,其通過三個(gè)接口方法addView()照雁、updateViewLayout()蚕愤、removeView()來管理View,也就是說Window是View的管理者饺蚊。
WindowManager.addView()
調(diào)用到WindowManagerGlobal.addView()
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView, userId);
ViewRootImpl.setView()
public void setView(...){
......
requestLayout();
}
public void requestLayout() {
......
scheduleTraversals();
}
void scheduleTraversals() {
......
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
mChoreographer.postCallback()
也就是執(zhí)行mTraversalRunnable這個(gè)Runnable
ViewRootImpl.mTraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
ViewRootImpl.doTraversal()
void doTraversal() {
......
performTraversals();
}
private void performTraversals() {
//繪制流程
performMeasure();
performLayout();
performDraw();
}
WindowManager.addView()
最終走到ViewRootImpl.performTraversals()
方法萍诱,內(nèi)部依次調(diào)用performMeasure()、performLayout()污呼、performDraw()裕坊,從DecorView開始遍歷View樹對(duì)應(yīng)調(diào)用View.measure()、View.layout()燕酷、View.draw()直到View樹完成繪制流程籍凝。可以下結(jié)論苗缩,ViewRootImpl接管了繪制流程饵蒂。
這里還有個(gè)問題,mTraversalRunnable何時(shí)執(zhí)行run()方法呢酱讶,下面回看Choreographer.postCallback()
退盯,經(jīng)過一些列調(diào)用,調(diào)用到內(nèi)部類FrameDisplayEventReceiver.scheduleVsync()
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
nativeScheduleVsync(mReceiverPtr)調(diào)用native方法發(fā)出VSync信號(hào)浴麻,回調(diào)到FrameDisplayEventReceiver.onVsync()
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
......
doFrame(mTimestampNanos, mFrame);
}
void doFrame(long frameTimeNanos, int frame) {
......
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
}
doFrame()就是執(zhí)行mTraversalRunnable走繪制流程了得问,也就是說VSync垂直同步信號(hào)到來時(shí)會(huì)執(zhí)行繪制流程刷新UI囤攀,按60幀算每16ms系統(tǒng)都會(huì)發(fā)出VSYNC信號(hào)软免。
簡(jiǎn)單總結(jié)下View樹如何顯示:在Activity的onResume()生命周期之后通過WindowManager.addView(),初始化ViewRootImpl焚挠,等待VSync垂直同步信號(hào)到來膏萧,調(diào)用到ViewRootImpl.performTraversals()開啟View樹繪制流程。
Activity是這個(gè)流程,那其它的View像Toast榛泛、Dialog呢蝌蹂?當(dāng)然也是一樣,Toast.show()曹锨、Dialog.show()都會(huì)調(diào)用到WindowManager.addView()孤个,至于之后的流程就完全和上述一致了。
Toast稍微麻煩點(diǎn)沛简,Toast.show()通過binder機(jī)制獲取NMS代理對(duì)象齐鲤,調(diào)用到NMS.enqueueToast()將Toast內(nèi)部類TN做為回調(diào)對(duì)象傳入。NMS經(jīng)過一系列調(diào)用最終回調(diào)到TN.show()顯示Toast椒楣,并在顯示Duration時(shí)長(zhǎng)后回調(diào)TN.hide()取消Toast给郊。
TN.show()
final Handler mHandler;
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
......
}
}
};
}
public void handleShow(IBinder windowToken) {
......
mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
mHorizontalMargin, mVerticalMargin,
new CallbackBinder(getCallbacks(), mHandler));
}
}
ToastPresenter.show()
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
......
try {
//重點(diǎn)
mWindowManager.addView(mView, mParams);
} catch (WindowManager.BadTokenException e) {
Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
return;
}
}
Dialog.show()
public void show() {
......
//DecorView
mDecor = mWindow.getDecorView();
//重點(diǎn)
mWindowManager.addView(mDecor, l);
}
依然是WindowManager.addView()。下面看改變View的屬性時(shí)調(diào)用的View.invalidate()
捧灰、改變View的大小時(shí)調(diào)用的View.requestLayout()
淆九。
View.invalidate()
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
final ViewParent p = mParent;
p.invalidateChild(this, damage);
}
/**
* The parent this view is attached to.
* {@hide}
*
* @see #getParent()
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected ViewParent mParent;
ViewParent也就是父View,往上調(diào)用直到ViewRootImpl.invalidateChildInParent()
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
......
invalidateRectOnScreen(dirty);
}
private void invalidateRectOnScreen(Rect dirty) {
......
scheduleTraversals();
}
void scheduleTraversals() {
......
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
流程又走到了熟悉的地方毛俏,Choreographer.postCallback()炭庙,傳入mTraversalRunnable,等待Vsync信號(hào)回調(diào)mTraversalRunnable.run()走繪制流程拧抖。
View.requestLayout()
public void requestLayout() {
......
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
ViewRootImpl.requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
仍然是熟悉的流程煤搜,這里看個(gè)大概,實(shí)際調(diào)用鏈內(nèi)部代碼還是有些復(fù)雜的唧席;需知刷新UI最終都要走ViewRootImpl.performTraversals()
擦盾。
View.post()
由前面流程分析可知View樹在onResume()之后才開始走繪制流程,那我們經(jīng)常使用的View.post()在onCreate()中如何獲取到View的寬高屬性呢淌哟?其實(shí)思路很簡(jiǎn)單迹卢,把post傳入的Runnable保存下來,等待View樹繪制完畢再回調(diào)run()方法徒仓,具體調(diào)用鏈就不再跟了腐碱。