作爲一名android開發不管你幾年經驗,view的繪製流程熟記於心總少不了吧,今天帶大家走一遍,也給自己加深印象。
setContentView是我們用來給activity設置我們寫的佈局界面,我們就從這裏入手。
Activity#setContentView
@UnsupportedAppUsage
private Window mWindow;
public Window getWindow() {
return mWindow;
}
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到,在activity調用了mWindow.setContentView方法,而window是抽象類,所以我們得找它的子類PhoneWindow
PhoneWindow#setContentView
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
首先判斷mContentParent是否爲空,是會調用installDecor()方法做些初始化工作。然後再通過LayoutInflater將佈局文件加載到mContentParent上面去。
PhoneWindow#installDecor
private DecorView mDecor;
private ViewGroup mContentParent;
private ViewGroup mContentRoot;
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mDecor.makeOptionalFitsSystemWindows();
//...
}
}
可以看到mDecor是DecorView類對象,而DecorView繼承自FrameLayout,generateDecor()方法就是初始化創建一個空的FrameLayout,generateLayout(mDecor) 用來初始化mContentParent。
PhoneWindow#generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
requestFeature(FEATURE_ACTION_BAR);
}
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
//...
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {}
//...
int layoutResource;
//...
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...
return contentParent;
}
可以看到,generateLayout()方法會根據我們Acivity主題樣式,選擇加載不同的系統佈局資源,並將該視圖添加到DecorView中去。
到這裏只是將佈局文件綁定到Activity --> mWindow --> DecorView --> mContentParent 上,此時界面還未開始繪製。這時我們需要去到關鍵類ActivityThread類當中了。
ActivityThread#handleResumeActivity
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
//...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
//...
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
//...
}
可以看到調用了 WindowManager.addView()方法,而WindowManager是一個接口,也就是調用WindowManagerGlobl.addView()方法。
WindowManagerGlobl#addView():
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
該方法中實例化了 ViewRootImpl,並且調用了ViewRootImpl.setView()方法來持有當前的DecorView。
ViewRootImpl#setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//..
requestLayout();
//...
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
//校驗所在線程,mThread是在ViewRootImpl初始化的時候執行mThread = Thread.currentThread()進行賦值的,也就是初始化ViewRootImpl所在的線程。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
//做任務
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
//執行任務
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
可以看到,經過層層調用,最後調用到 ViewRootImpl.performTraversals();方法,這也就是view開始繪製的方法。
ViewRootImpl.performTraversals();
private void performTraversals() {
//...
//開始進行佈局準備
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null) {
//...
if (!mStopped) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
//...
if (measureAgain) {
//View的測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
//...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
//...
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
//...View的繪製
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
可以看到分別調用了 performMeasure() 、performLayout() 、performDraw() 方法,分別對應了View的measure()、layout()、draw()。根據根佈局的測量模式以及寬高生成對應的MeasureSpec,通過performMeasure()方法將父佈局的measureSpec傳遞給view.measure()。
MeasureSpec:
MeasureSpec是View的內部類,它封裝了一個View的尺寸,在onMeasure()當中會根據這個MeasureSpec的值來確定View的寬高。MeasureSpec的值保存在一個int值當中。一個int值有32位,前兩位表示模式mode後30位表示大小size。即MeasureSpec = mode + size。
在MeasureSpec當中一共存在三種mode:UNSPECIFIED、EXACTLY 和AT_MOST。
View#measure:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//關鍵方法...
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//...
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在該方法當中根據父佈局傳遞過來的MeasureSpec和自己的LayoutParmas生成自己的MeasureSpec,然後通過onMeasure()方法調用getDefaultSize() 拿到MeasureSpec存放的測量模式以及size,然後通過setMeasuredDimension() 來設置測量到的寬高,中間還調用了getSuggestedMinimumWidth()/getSuggestedMinimumHeight(),該方法判斷是否有背景,無背景則返回最小寬度或高度,有背景則返回背景的最小寬度或高度,只在測量模式爲UNSPECIFIED的時候設置此值。
這裏需要注意: view的MeasureSpec是由自己的LayoutParams和父容器所給的MeasureSpec共同決定的。原因如下:
ViewGroup#getChildMeasureSpec()
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
......
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
......
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
......
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
......
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
......
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
......
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
......
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
......
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
......
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
......
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
在父容器的specMode爲EXACTLY時,一切正常(子View尺寸指定爲match_parent或精確的dimen值時,Mode = EXACTLY,尺寸指定爲wrap_content則Mode = AT_MOST);
當父容器specMode爲AT_MOST的時候,可以看到,除了指定了dimen值之外,無論設置爲match_parent或wrap_content,Mode最終都是會變成AT_MOST;
如果父容器specMode是UNSPECIFIED的話,都是會變成UNSPECIFIED的。
View#layout
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
//..
}
//...
}
layout()過程,對於View來說用來計算View的位置參數,對於ViewGroup來說,除了要測量自身位置,還需要測量子View的位置。在layout()方法中已經通過setOpticalFrame(l, t, r, b)或 setFrame(l, t, r, b)方法對View自身的位置進行了設置,所以onLayout(changed, l, t, r, b)方法主要是ViewGroup對子View的位置進行計算viewGroup中需要重寫onLayout方法。
View#draw:
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
//...
// Step 2, save the canvas' layers
//....
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
//....
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
可以看到,註釋中draw方法內部有六個方法,其中第二和第五個方法不是必要的。當然了,方法中還註釋了第七個方法,都記住是最好的了。
1:drawBackground(canvas):作用就是繪製View的背景。
2:非必要方法 保存畫布的圖層。
3:onDraw(canvas):繪製View的內容。View的內容是根據自己需求自己繪製的,所以方法是一個空方法,View的繼承類自己複寫實現繪製內容。 canvas是從ViewRootImpl中的Surface傳遞進來的
4:dispatchDraw(canvas):遍歷子View進行繪製內容。在View裏面是一個空實現,ViewGroup裏面纔會有實現。View的繪製過程的傳遞通過dispatchDraw來實現的,dispatchDraw會遍歷調用所有子元素的draw方法,如此draw事件就一層層地傳遞了下去。在自定義ViewGroup一般不用複寫這個方法,因爲它在裏面的實現幫我們實現了子View的繪製過程,基本滿足需求。
5:非必要方法 繪製邊緣和恢復畫布的圖層。
6:onDrawForeground(canvas):繪製裝飾(前景色、滾動條)。
7:drawDefaultFocusHighlight(canvas):繪製默認焦點高亮。
還有一個地方不知道大家注意沒有,在ActivityThread.handleResumeActivity方法的最後,調用了 r.activity.makeVisible(); ,這也就是Activity在onResume()後可見的原因。
就是以上這些,大家一定要牢牢記住,將整個流程捋順了。