Activity要作的事情比較多,爲了解耦以及複用,將外觀和行爲的管理放到window
中,讓window
負責view
的建立管理以及與viewRootImpl
的交互。java
PhoneWindow
:window
的惟一實現。DecorView
:window
的頂層視圖,包含窗口裝飾。繼承自FrameLayout
,是一個視圖真實的根。和window互相持有對方。ContentParent
:DecorView
的子view,放置窗口內容視圖。便可以認爲是DecorView
自己,也能夠認爲它是DecorView
的用來存放內容視圖的子級。當在Activity
中經過setContentView()
設置視圖的時候,會層層傳遞到PhoneWindow.setContentView()
中,經過addView()
或者inflate()
添加到ContentParent
中。canvas
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (...) {
...
} else {
mContentParent.addView(view, params);
}
...
}
複製代碼
DecorView
是最頂層的View
,是視圖樹的的根,整個視圖樹的繪製從這裏開始。安全
DecorView
的繪製由ViewRootImpl
控制,ViewRootImpl.performTraversals()
前後調用了performMeasure()
、peformLayout()
、performDraw()
,在其中,分別對應調用了DecorView
的measure()
、layout()
、draw()
方法,進行對應的繪製流程。markdown
private void performTraversals() {
...
performMeasure();
...
performLayout();
...
performDraw();
}
private void performMeasure() {
...
mView.measure();
}
private void performLayout() {
...
mView.layout();
}
private void performDraw() {
...
mView.draw();
}
複製代碼
ViewRootImpl
工做的起點是在ViewRootImpl.setView()
被調用的時候。ViewRootImpl.setView()
主要的工做是:佈局
將DecorView
存入ViewRootImpl
。post
調用requestLayout()
啓動繪製。this
將ViewRootImpl
做爲parent
存入DecorView
,完成相互的綁定。spa
void setView(View view,...) {
if (mView == null) {
mView = view;
...
requestLayout();
...
view.assignParent(this);
}
}
複製代碼
ViewRootImpl.addView()
調用鏈:線程
ActivityThread.handleResumeActivity()
WindowManagerImpl.addView()
WindowManagerGlobal.addView()
,在其中建立ViewRootImpl
並調用ViewRootImpl.setView()
根據這個調用鏈能夠知道,resume
以後纔開始進行繪製流程,因此以前view
的寬高並無計算出來,讀取不到view
的寬高。另外,在resume
以前的操做並不必定真正的繪製出來。code
ViewRootImpl.requestLayout()
調用的時候,不是當即開始繪製,而是將回調綁定到下一幀,在指定的時刻開始回調到performTraversals()
,開始繪製。另外能夠看到ViewRootImpl.requestLayout()
中才進行了線程檢查,這就解釋了在ViewRootImpl
被初始化以前,不會進行線程安全的檢查,即在初始化以前在其餘線程對view的數據進行更改可能不會報錯。這也是onCreate()
中不在主線程更新view
數據不會報錯的緣由。
public void requestLayout() {
if (...) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (...) {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
複製代碼
View.requestLayout()
遞歸調用parent.requestLayout()
,最終遞歸到ViewRootImpl.requestLayout()
中,從新進行繪製,只會走measure
和layout
流程。
public void requestLayout() {
...
if (...) {
mParent.requestLayout();
}
}
複製代碼
View.invalidate()
遞歸調用parent.invalidateChild()
,最終遞歸到ViewRootImpl.invalidateChild()
中從新繪製,只會走draw
流程。遞歸時傳遞了重繪的位置,只會對調用的view
的位置進行重繪。
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;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
}
複製代碼
ViewGroup.addView()
中前後調用了requestLayout()
和invalidate()
,從新進行繪製。前面說過在setContentView()
的時候,是經過addView()
進行添加,可是這個調用的時間在resume
以前,這個時候ViewRootImpl
尚未初始化,DecorView
的parent
還不是VIewRootImpl
,因此並不會開始繪製。
public void addView(View child, int index, ViewGroup.LayoutParams params) {
...
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
複製代碼
繪製流程分紅三個步驟:measure
,layout
,draw
。
measure
過程當中,會層層傳遞widthMeasureSpec
和heightMeasureSoec
。MesureSpec
是View
內的一個靜態類,封裝了父級傳遞到子級的佈局要求,每一個MeasureSpec
表示了對高度寬度寬度的要求。使用32位int來表示,高2位表示測量模式,低30位表示測量尺寸。測量模式有三個:
USPECIFIED
:00,父佈局不對子級施加任何約束。EXACTLY
:01,父佈局已經肯定子級的大小,好比match_parent或者xxxdp。AT_MOST
:10,自適應,子級在父佈局給定的範圍內肯定本身的大小。MeasureSpec
中還封裝了幾個方法,方便對於MeasureSpec
的建立以及拆解。
public static class MeasureSpec {
public static int makeMeasureSpec(int size, int mode) {
...
return size + mode;
}
public static int getMode(int measureSpec) {
...
}
public static int getSize(int measureSpec) {
...
}
}
複製代碼
DecorView.measure()
執行的是View.measure()
方法,能夠看到這是個final
方法,也就是說全部view的measure都是走的這裏,在標誌了強制刷新(forceLayout
)或者尺寸並未被正確測量(needsLayout
)的狀況下,會調用onMeasure()
進行測量。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (forceLayout || needsLayout) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
複製代碼
對於不是ViewGroup
的View
,在onMeasure()
中只須要測量自身便可,通常會調用View.onMeasure(),在其中計算大小,並調用setMeasureDimension()
儲存測量的寬高。
在getDefaultSize()計算寬高的時候,對於AT_MOST和EXACTLY都返回父類要求的最大大小,即wrap_content
和match_parent
取的計算結果都至關於match_parent
,若是直接繼承View
實現自定義view的時候,須要注意處理這種狀況。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = View.MeasureSpec.getMode(measureSpec);
int specSize = View.MeasureSpec.getSize(measureSpec);
switch (specMode) {
case View.MeasureSpec.UNSPECIFIED:
result = size;
break;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
複製代碼
Decorview.onMeasure()
執行的是FrameLayout.onMeasure()
方法。主要的步驟是:
ViewGroup.meaureChildWithMargins()
測量子view大小,並取得子view中最大的寬高,做爲自身可實現要求的最小寬高。resolveSizeAndState()
與父級傳遞的佈局要求綜合計算得到最終寬高。match_parent
的子view超過一個,會從新對這些view進行計算寬高,由於這些view寬高受到FrameLayout
的寬高的影響,以前的計算結果可能並不正確。protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (...) {
measureChildWithMargins();
final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
...
if (lp.width == FrameLayout.LayoutParams.MATCH_PARENT ||
lp.height == FrameLayout.LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
...
setMeasuredDimension(resolveSizeAndState(maxWidth, ...),
resolveSizeAndState(maxHeight, ...));
count = mMatchParentChildren.size();
if (count > 1) {
...
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
複製代碼
ViewGroup.measureChildWithMargins()
,主要有兩個工做
measure()
到這裏就完成了一次完整的遞歸,又調用到了子view的measure()
,以後再繼續根據view類型繼續向下傳遞,完成整個view樹的計算。
protected void measureChildWithMargins(...) {
...
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼
Decorview.layout()
執行的是ViewGroup.layout()
,主要調用的是super
的方法,也就是View.layout()
。
public final void layout(int l, int t, int r, int b) {
if (...) {
...
super.layout(l, t, r, b);
}
}
複製代碼
View.onLayout()
首先調用setFrame()
,判斷對其父view的位置是否發生改變,根據是否發生改變調用onLayout()
。
public void layout(int l, int t, int r, int b) {
...
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || ...) {
onLayout(changed, l, t, r, b);
...
}
}
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
...
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
...
}
return changed;
}
複製代碼
onLayout()
主要完成對子view的從新佈局,對於不是ViewGroup
的View
不包含子View,調用View.onLayout()
,是個空方法;而ViewGroup.onLayout()
是abstract
,須要不一樣的佈局管理器實現不一樣的佈局方法。
DecorView
會執行到FrameLayout.onlayout()
,計算出子view的位置信息,並調用子view的layout()
方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
...
for (int i = 0; i < getChildCount(); i++) {
...
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
複製代碼
Decorview
重寫了draw()
方法,調用父類方法,也就是View.draw()
,以後作了菜單背景的繪製。
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
複製代碼
View.draw(),官方註釋了繪製步驟:
其中2和5只在須要的狀況下才會執行,主要的繪製流程是1,3,4。
public void draw(Canvas canvas) {
...
// Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
...
// Step 4, draw the children
dispatchDraw(canvas);
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
...
}
複製代碼
View.drawBackgroud()
,背景不爲空的狀況下,最終調用Drawable.draw()
完成背景繪製。
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
...
background.draw(canvas);
}
複製代碼
DecorView
也重寫了ondraw()
,在其中也是先調用父類方法,而後繪製了自身,View.onDraw()
是個空方法,每一個view內容各不相同,須要對應的去實現各自的繪製方法,就好比DecorView.ondraw()
,在其中繪製後備背景,完成了自身的繪製。
public void onDraw(Canvas c) {
super.onDraw(c);
mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
mStatusColorViewState.view, mNavigationColorViewState.view);
}
複製代碼
DecorView.dispatchDraw()
執行的是ViewGroup.dispatchDraw()
,在其中遍歷子view並調用drawChild()
方法。對於不是ViewGroup
的View
來講,自己就沒有子view,這個方法就沒有什麼意義,因此View.dispatchDraw()
是個空方法。
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) {
...
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || ...) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
複製代碼
ViewGroup.drawChild()
會調用到View中另外一個有boolean
返回值draw()
方法(View
中有兩個draw()
方法),其中仍是會調用到沒有返回值的draw()
方法。官方註釋解釋,這個有boolean
的draw()
方法,只是提供給ViewGroup.drawChild()
使用,這個方法的主要工做是根據圖層類型(CPU
or GPU
)進行渲染和硬件加速的地方。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
if (...) {
...
draw(canvas);
}
...
}
複製代碼
到這裏完成了draw
一次遞歸,最終完成整個樹的繪製。