在分析三個方法以前, 先梳理一下 ViewRootImpl.performTraversals, 若是不清楚該方法的做用的話, 請先閱讀這篇博文 https://www.jianshu.com/p/2aac4e679549bash
/**
* ViewRootImpl.performTraversals
*/
public void performTraversals() {
// 1. 經過 mLayoutRequested 和 (!mStopped || mReportNextDraw) 構造 layoutRequested 變量
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
// 2. 判斷 window 是否須要改變, 若 layoutRequested 爲 false, windowShouldResize 也爲 false
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
// 3. 其中一個知足便可進入這個 if 語句
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
/// 3.1 判斷 View 的寬高是否有改變, 沒有的話, 不會調用 performMeasure
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 3.1.1 執行 performMeasure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 3.1.2 改變了 layoutRequested 的值
layoutRequested = true;
}
}
// 4. 若執行了 performMeasure, 通常狀況下也會執行 performLayout
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
// 5. 判斷是否取消了繪製
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// 5.1 知足條件則執行 performDraw()
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
// Traversals 結束
mIsInTraversal = false;
}
複製代碼
梳理了 ViewRootImpl.performTraversals 以後, 能夠看到對於 View 繪製三大流程中的前兩個,app
這裏大膽猜測一下 View.requestLayout 時 mLayoutRequested 這個 Flag 會變動爲 true, 接下來帶着問題來看看 View.requestLayout 的過程ide
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
/******************performLayout 的第一階段*******************/
mLayoutRequested = false;
mScrollMayChange = true;
// layout 執行標記位
mInLayout = true;
final View host = mView;//mView 爲 DecorView
if (host == null) return;
try {
// 1.1 執行第一次 layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// layout結束標記位
mInLayout = false;
/******************performLayout 的第二階段*******************/
// 檢查在進行 layout 的過程當中, 是否有 View 調用了 requestLayout 方法
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// mLayoutRequesters 就是在 mInLayout = true 的過程當中 requestLayout 的 view 集合
// 在 ViewRootImpl.requestLayoutDuringLayout 代碼中有所體現
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false);
if (validLayoutRequesters != null) {
// 2.1 將 mHandlingLayoutInLayoutRequest 標記爲 true
mHandlingLayoutInLayoutRequest = true;
// 處理新的佈局請求
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
// 在第一次 layout 執行結束後, 運行第二次 layout 請求
view.requestLayout();
}
// 2.2 執行二次 Measure
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
// 2.3 執行二次 layout
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// 2.4 將 mHandlingLayoutInLayoutRequest 標記爲 false
mHandlingLayoutInLayoutRequest = false;
// 2.5 第三次檢查 view 的 requestLayout 請求
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
// 2.5.1 在第二次 layout 的過程當中, 將請求 post 出去
view.requestLayout();
}
}
});
}
}
}
}
// 2.5 第二次 layout 結束的標誌
mInLayout = false;
}
複製代碼
能夠看到在 performLayout 的過程當中,佈局
/**
* View.requestLayout
*/
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
ViewRootImpl viewRoot = getViewRootImpl();
// 1. 判斷當前 ViewRootImpl 是否正在 Layout
if (viewRoot != null && viewRoot.isInLayout()) {// isInLayout() 爲 true 的條件是 mInLayout = true
// 1.1 判斷當前 View 是否能夠在 ViewRootImpl 正在進行 Layout 時, 繼續執行發起 requestLayout
if (!viewRoot.requestLayoutDuringLayout(this)) {
// 返回 false , 則說明不容許, 則直接讓這次 View.requestLayout() return
return;
}
}
// 2 將本身的狀態標記爲正在 requestLayout
mAttachInfo.mViewRequestingLayout = this;
}
// 3. 給當前 View 打上標記 PFLAG_FORCE_LAYOUT | PFLAG_INVALIDATED
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
// 4. 嘗試調用父容器的 requestLayout
if (mParent != null && !mParent.isLayoutRequested()) {
// 4.1 調用 ViewParent 的 requestLayout, 即調用父容器的 requestLayout
// ViewGroup 中並無重寫 requestLayout 方法, 即還會調用到 View 的 requestLayout 方法中去
// 此方法會回溯到 當前 Window 的 ViewRootImpl 中的 requestLayout 中去
mParent.requestLayout();
}
// 5. 執行到這裏, 說明當前 View 的 requestLayout 已經完成, 將 mAttachInfo.mViewRequestingLayout 置空
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
/**
* ViewRootImpl.requestLayoutDuringLayout
*/
boolean requestLayoutDuringLayout(final View view) {
// 1.1.1 說明傳入的 view 即爲 ViewRootImpl, 由於只有它的 mParent 爲null
if (view.mParent == null || view.mAttachInfo == null) {
// Would not normally trigger another layout, so just let it pass through as usual
return true;
}
// 1.1.2 將請求 Layout 的 View 添加到 ViewRootImpl 中維護的集合 mLayoutRequesters 中
if (!mLayoutRequesters.contains(view)) {
mLayoutRequesters.add(view);
}
if (!mHandlingLayoutInLayoutRequest) {
// 1.1.3 mHandlingLayoutInLayoutRequest 爲 false
// 說明當前 ViewRootImpl 的 performLayout() 沒有進行 second layout, 它將會在第二次 layout 中執行
return true;
} else {
// 1.1.4 mHandlingLayoutInLayoutRequest 爲 true
// 說明當前 ViewRootImpl 的 performLayout() 正在進行 second layout, 此時 view 的 requestLayout 會被 post 到下一幀
// return false; 由上面代碼可知 View 的 requestLayout() 請求會被直接 return;
return false;
}
}
/**
* View.isLayoutRequested
*/
public boolean isLayoutRequested() {
// 當 View 進行過 requestLayout() 時, mPrivateFlags |= PFLAG_FORCE_LAYOUT;
// 則會返回 true
return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
}
/**
* ViewRootImpl.requestLayout
* View.requestLayout() 最終的走向
*/
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
// 將當前 mLayoutRequested 標記爲 true, 該 Flag 會直接影響到 performTraversals 是否執行 measure 和 layout
mLayoutRequested = true;
// 這裏又從新開啓了 View 繪製的三大流程
scheduleTraversals();
}
}
複製代碼
從上述代碼可知 View.requestLayout 方法主要作了如下事情post
View.invalidate 方法會調用到 ViewGroup.invalidateChild 中ui
/**
* ViewGroup.invalidateChild
*/
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
// child 的 parent 指定爲自身
ViewParent parent = this;
if (attachInfo != null) {
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
// 會遞歸的調用 parent 的 invalidateChildInParent 方法
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
}
}
複製代碼
能夠看到 View.invalidate 方法會遞歸的調用 parent.invalidateChildInParent 方法, 直至回溯到 ViewRootImpl 中, 與 requestLayout 一模一樣, ViewRootImpl 重寫了 invalidateChildInParent 方法, 接下來看看, 它作了什麼this
/**
* ViewRootImpl.invalidateChildInParent
*/
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (dirty == null) {
// 1. 直接調用了 invalidate 方法
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
// 2.調用了 invalidateRectOnScreen 方法, 刷新區域內的視圖
invalidateRectOnScreen(dirty);
return null;
}
/**
* ViewRootImpl.invalidate
*/
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
// 看到了最熟悉的 scheduleTraversals
scheduleTraversals();
}
}
/**
* ViewRootImpl.invalidateRectOnScreen
*/
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
// 看到了最熟悉的 scheduleTraversals
scheduleTraversals();
}
}
複製代碼
看到 ViewRootImpl 中 invalidateChildInParent 最終都回調了 scheduleTraversals 方法, 開啓了 View 繪製的三大流程spa
/**
* View.postInvalidate
*/
public void postInvalidate() {
postInvalidateDelayed(0);
}
/**
* View.postInvalidateDelayed
*/
public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 到 ViewRootImpl 中分發
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
複製代碼
能夠看到 View 調用 postInvalidate 時, 最終會流入 ViewRootImpl 中, 接下里看看 ViewRootImpl 作了哪些操做線程
/**
* ViewRootImpl.dispatchInvalidateDelayed
*/
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
// 在 mHandler 綁定的線程中調用了 View 的 invalidate
((View) msg.obj).invalidate();
break;
}
複製代碼
能夠看到 View.postInvalidate 本質上仍是調用了 View.invalidate(), 它在調用以前會加入消息隊列, 投遞到 Handler 建立線程去執行, 也就是在非 UI 線程咱們想從新繪製時, 咱們能夠採用 postInvalidate 這種方式code
不得不歎服SDK開發者的技巧, 其中有不少細節都沒有兼顧到, 只是爲了釐清 requestLayout, invalidate, postInvalidate 這三者的工做流程, 與部分細節, 可能有不少不到位的地方, 但願可以批評指出