1、View是如何被添加到屏幕窗口上
1、創(chuàng)建頂層布局容器DecorView
2、在頂層布局中加載基礎(chǔ)布局ViewGroup
3、將ContentView添加到基礎(chǔ)布局中的FrameLayout中
從我們的activity的onCreate()中的setContentView開始

跳到了Window類中的setContentView方法,從描述The only existing implementation of this abstract class is android.view.PhoneWindow我們知道PhoneWindow是Window的唯一實(shí)現(xiàn)類。

PhoneWindow中的setContentView方法主要做了兩件事情

第一件事情:




generateLayout這個(gè)方法比較長(zhǎng),主要有三個(gè)步驟:
1、通過(guò)Window Style樣式,設(shè)置Window風(fēng)格
2、加載系統(tǒng)中對(duì)應(yīng)的layoutResource,作為基礎(chǔ)布局(基礎(chǔ)容器),根據(jù)不同的features,初始化不同的布局。如:layoutResource = R.layout.screen_simple;(開發(fā)者自己通過(guò)setContentView(int layoutId)設(shè)置的布局文件就是添加到其中的)
3、當(dāng)資源加載完畢,會(huì)調(diào)用DecorView的onresourcesLoaded()方法,進(jìn)而將上述基礎(chǔ)布局添加到頂層布局mDecorView中。
第二件事情:

布局層次如下圖:

2、將View樹添加到Window中

ActivityThread是Activity的啟動(dòng)入口, 在它有一個(gè)內(nèi)部類H繼承Handler,專門用來(lái)處理主線程的消息。
class H extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
}
break;
啟動(dòng)一個(gè)Activity,會(huì)觸發(fā)handleLaunchActivity()方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
......
// 1、創(chuàng)建Activity
Activity a = performLaunchActivity(r, customIntent);
......
// 2、調(diào)用activity的onResume方法
handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
我們先看一下performLaunchActivity()方法的源碼:
private ActivityperformLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity =null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 1、創(chuàng)建Activity
activity =mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
} catch (Exception e) {
}
// 2、創(chuàng)建Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// 3、創(chuàng)建Window
Window window =null;
if (r.mPendingRemoveWindow !=null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow =null;
r.mPendingRemoveWindowManager =null;
}
// 4、關(guān)聯(lián)activity和window
activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback);
通過(guò)源碼,可以看到,performLaunchActivity()方法主要是創(chuàng)建Activity、Application、Window對(duì)象,并關(guān)聯(lián)到一起。緊接著,調(diào)用activity的onCreate()方法,進(jìn)而調(diào)用setContentView()方法,構(gòu)建View樹。
然后我們?cè)倏匆幌耯andleResumeActivity()方法的源碼:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// 1、創(chuàng)建 ViewManager 對(duì)象
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded =true;
// 2、將DecorView添加到Window中
wm.addView(decor, l);
}else {
a.onWindowAttributesChanged(l);
}
}
在該方法內(nèi)部,創(chuàng)建ViewManager對(duì)象,并調(diào)用addView()方法,將DecorView添加到Window中。(通過(guò)查看源碼,ViewManager.addView() --> WindowManager.addView() --> WindowManagerGlobal.addView(),這不是本文的重點(diǎn),就不一一列舉了)
至此,View樹就已經(jīng)添加到Window中,但是此時(shí)用戶還看不到界面,因?yàn)檫€沒有進(jìn)行繪制。所以接下來(lái)就需要將該View繪制出來(lái)
3、View的繪制流程
我們看一下WindowManagerGlobal.addView(),在該方法內(nèi)部會(huì)調(diào)用ViewRootImpl的setView()方法,在該方法內(nèi)部,會(huì)調(diào)用requestLayout()方法,進(jìn)而出發(fā)View的繪制流程:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 1、檢查是否在主線程
checkThread();
// 2、執(zhí)行traversal
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
final TraversalRunnablemTraversalRunnable =new TraversalRunnable();
final class TraversalRunnableimplements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
performTraversals();
}
}
}
private void performTraversals() {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
從源碼中,可以看到,最終在performTraversals()方法中,執(zhí)行了View的測(cè)量、布局和繪制過(guò)程。
至此,View繪制完畢后,就可以顯示到屏幕上了。

MeasureSpec
MeasureSpec 表示的是一個(gè) 32 位的整數(shù)值,它的高 2 位表示測(cè)量模式 SpecMode,低 30 位表示某種測(cè)量模式下的規(guī)格大小 SpecSize。MeasureSpec 是 View 類的一個(gè)靜態(tài)內(nèi)部類,用來(lái)說(shuō)明應(yīng)該如何測(cè)量這個(gè)View。
三種測(cè)量模式。
UNSPECIFIED:不指定測(cè)量模式,父視圖沒有限制子視圖的大小,子視圖可以是想要的任何尺寸,通常用于系統(tǒng)內(nèi)部,應(yīng)用開發(fā)中很少使用到。
EXACTLY:精確測(cè)量模式,當(dāng)該視圖的 layout_width 或者 layout_height 指定為具體數(shù)值或者 match_parent 時(shí)生效,表示父視圖已經(jīng)決定了子視圖的精確大小,這種模式下 View 的測(cè)量值就是 SpecSize 的值。
AT_MOST:最大值模式,當(dāng)前視圖的 layout_width 或者 layout_height 指定為 wrap_content 時(shí)生效,此時(shí)子視圖的尺寸可以是不超過(guò)父視圖運(yùn)行的最大尺寸的任何尺寸。
對(duì) DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同決定;對(duì)于普通的 View,它的 MeasureSpec 由父視圖的 MeasureSpec 和其本身的 LayoutParams 共同決定。
//父容器不對(duì)View做任何限制,系統(tǒng)內(nèi)部使用
public static final int UNSPECIFIED = 0 << MODE_SHIFT;//00000000000000000000000000000000
//父容器檢測(cè)出View的大小,Vew的大小就是SpecSize LayoutPamras match_parent 固定大小
public static final int EXACTLY = 1 << MODE_SHIFT;//01000000000000000000000000000000
//父容器指定一個(gè)可用大小,View的大小不能超過(guò)這個(gè)值,LayoutPamras wrap_content
public static final int AT_MOST = 2 << MODE_SHIFT; //10000000000000000000000000000000
Measure
Measure 用來(lái)計(jì)算 View 的實(shí)際大小。頁(yè)面的測(cè)量流程從 performMeasure 方法開始。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
具體操作是分發(fā)給 ViewGroup 的,由 ViewGroup 在它的 measureChild 方法中傳遞給子 View。ViewGroup 通過(guò)遍歷自身所有的子 View,并逐個(gè)調(diào)用子 View 的 measure 方法實(shí)現(xiàn)測(cè)量操作。
// 遍歷測(cè)量 ViewGroup 中所有的 View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
// 測(cè)量某個(gè)指定的 View
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
View (ViewGroup) 的 Measure 方法,最終的測(cè)量是通過(guò)回調(diào) onMeasure 方法實(shí)現(xiàn)的,這個(gè)通常由 View 的特定子類自己實(shí)現(xiàn),可以通過(guò)重寫這個(gè)方法實(shí)現(xiàn)自定義 View。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
....
}
// 如果需要自定義測(cè)量,子類需重寫這個(gè)方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 如果 View 沒有重寫onMeasure 方法,默認(rèn)會(huì)直接調(diào)用 getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
ViewGroup measure --> onMeasure(測(cè)量子控件的寬高)--> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的寬高)
View measure --> onMeasure --> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的寬高)
Layout
Layout 過(guò)程用來(lái)確定 View 在父容器的布局位置,他是父容器獲取子 View 的位置參數(shù)后,調(diào)用子 View 的 layout 方法并將位置參數(shù)傳入實(shí)現(xiàn)的。ViewRootImpl 的 performLayout 代碼如下。
ViewGroup layout(來(lái)確定自己的位置,4個(gè)點(diǎn)的位置) -->onLayout(進(jìn)行子View的布局)
View layout(來(lái)確定自己的位置,4個(gè)點(diǎn)的位置)
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
View 的 layout 方法代碼。
public void layout(int l, int t, int r, int b) {
onLayout(changed, l, t, r, b);
}
// 空方法,子類如果是 ViewGroup 類型,則重寫這個(gè)方法,實(shí)現(xiàn) ViewGroup 中所有 View 控件布局
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
Draw
ViewGroup
繪制背景 drawBackground(canvas)
繪制自己onDraw(canvas)
繪制子View dispatchDraw(canvas)
繪制前景,滾動(dòng)條等裝飾onDrawForeground(canvas)
View
繪制背景 drawBackground(canvas)
繪制自己onDraw(canvas)
繪制前景,滾動(dòng)條等裝飾onDrawForeground(canvas)
onMeasure --> onLayout(容器) --> onDraw(可選)
Draw 操作用來(lái)將控件繪制出來(lái),繪制的流程從 performDraw 方法開始。performDraw 方法在類 ViewRootImpl 內(nèi),其核心代碼如下。
private void performDraw() {
boolean canUseAsync = draw(fullRedrawNeeded);
}
private boolean draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
mView.draw(canvas);
...
}
最終調(diào)用到每個(gè) View 的 draw 方法繪制每個(gè)具體的 View,繪制基本上可以分為六個(gè)步驟。
public void draw(Canvas canvas) {
...
// Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
...
// Step 2, save the canvas' layers
saveCount = canvas.getSaveCount();
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}