- 最近在擼
Golang有點上火了,來整理下安卓源碼資料??? - 分析結(jié)果基于
Audroid API 26
requestLayout()源碼分析
- 假如在一個頁面上有個按鈕,點擊按鈕就對一個
view.requestLayout(),這個view執(zhí)行的方法如下:
InvalidateTextView------onMeasure
InvalidateTextView------onMeasure
InvalidateTextView-------layout
InvalidateTextView--------onLayout
InvalidateTextView----------draw
InvalidateTextView------------onDraw
-
view.requestLayout()方法的詳情
@CallSuper
public void requestLayout() {
// 清除繪制的緩存
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
//只有在布局邏輯中觸發(fā)請求,如果這是請求它的視圖,而不是其父層次結(jié)構(gòu)中的視圖
ViewRootImpl viewRoot = getViewRootImpl();
//如果連續(xù)請求兩次,其中一次自動返回!
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
//todo 為當(dāng)前view設(shè)置標記位 PFLAG_FORCE_LAYOUT
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
// todo 向父容器請求布局 這里是向父容器請求布局,即調(diào)用父容器的requestLayout方法,為父容器添加PFLAG_FORCE_LAYOUT標記位,而父容器又會調(diào)用它的父容器的requestLayout方法,即requestLayout事件層層向上傳遞,直到DecorView,即根View,而根View又會傳遞給ViewRootImpl,也即是說子View的requestLayout事件,最終會被ViewRootImpl接收并得到處理
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
- 1、如果緩存不為
null,清除繪制的緩存
if (mMeasureCache != null) mMeasureCache.clear();
- 2、這里判斷了是否在
layout,如果是,就返回,也就可以理解為: 如果連續(xù)請求兩次,并且其中的一次正在layout中,其中一次返回!這樣做是節(jié)約性能
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
//只有在布局邏輯中觸發(fā)請求,如果這是請求它的視圖,而不是其父層次結(jié)構(gòu)中的視圖
ViewRootImpl viewRoot = getViewRootImpl();
//如果連續(xù)請求兩次,其中一次自動返回!
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
- 3、 為當(dāng)前
view設(shè)置標記位PFLAG_FORCE_LAYOUT,關(guān)于|=符號:a|=b的意思就是把a和b按位或然后賦值給a 按位或的意思就是先把a和b都換成2進制,然后用或操作,相當(dāng)于a=a|b
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
- 4、向父容器請求布局,即調(diào)用
ViewGroup父容器的requestLayout()方法,為父容器添加PFLAG_FORCE_LAYOUT標記位,而父容器又會調(diào)用它的父容器的requestLayout()方法,即requestLayout()事件層層向上傳遞,直到DecorView,即根View,而根View又會傳遞給ViewRootImpl,也即是說子View的requestLayout()f事件,最終會被ViewRootImpl.requestLayout()接收并得到處理
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
-
5、
ViewRootImpl.requestLayout()方法詳情@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 檢查是否在主線程,不在的話,拋出異常 checkThread(); mLayoutRequested = true; scheduleTraversals(); } }- 1、檢查是否在主線程,不在的話,拋出異常
checkThread();
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }- 2 、最終走到這個方法來
ViewRootImpl.scheduleTraversals(),在其中可以看到一行非常有意思的代碼
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);,其中有個對象mTraversalRunnable,這樣下去就會重新的測量、布局和繪制;具體的流程可以看這篇文章Android源碼分析(View的繪制流程)
// requestLayout() 會調(diào)用這個方法 void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 最終調(diào)用的是這個方法 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } - 1、檢查是否在主線程,不在的話,拋出異常
有個問題,我先拋出結(jié)論,
requessLayout() 、invalidate()、postInvalidate()最終的底層調(diào)用的是ViewRootImpl.scheduleTraversals()的方法,為什么僅僅requessLayout()才會執(zhí)行onMeasure() onLayout() onDraw()這幾個方法?關(guān)于
view.measure()方法:在前面我們知道mPrivateFlags |= PFLAG_FORCE_LAYOUT所以forceLayout = true,也就是會執(zhí)行onMeasure(widthMeasureSpec, heightMeasureSpec);,同時執(zhí)行完了以后呢,最后為標記位設(shè)置為mPrivateFlags |=PFLAG_LAYOUT_REQUIRED
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// requestLayout的方法改變的 mPrivateFlags |= PFLAG_FORCE_LAYOUT; 所以 forceLayout = true
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
...
if (forceLayout || needsLayout) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//最終會走到這方法來
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 接著最后為標記位設(shè)置為PFLAG_LAYOUT_REQUIRED
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
...
}
- 關(guān)于
view.layout()方法:判斷標記位是否為PFLAG_LAYOUT_REQUIRED,如果有,則對該View進行布局,也就是走到onLayout(changed, l, t, r, b);,最后清除標記mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
//第二次調(diào)用這個方法,,,
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);
//判斷標記位是否為PFLAG_LAYOUT_REQUIRED,如果有,則對該View進行布局
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
// onLayout方法完成后,清除PFLAG_LAYOUT_REQUIRED標記位
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
}
// //最后清除PFLAG_FORCE_LAYOUT標記位
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
...
}
- 以上就是
requestLayout()的分析的結(jié)果:view調(diào)用了這個方法,其實會從view樹重新進行一次測量、布局、繪制這三個流程。 -
做了一張圖
requestLayout()的原理.jpg
invalidate()源碼分析
-
view.invalidate();繼承一個Textview,然后重寫方法,設(shè)置一個but,同時請求方法,打印日志:請求一次的輸出的結(jié)果
InvalidateTextView----------draw
InvalidateTextView------------onDraw
- 方法詳情 :
view.invalidate()
public void invalidate() {
invalidate(true);
}
- 該視圖的繪圖緩存是否也應(yīng)無效。對于完全無效,設(shè)置為true,但是如果視圖的內(nèi)容或維度沒有改變,則可以設(shè)置為false。
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
-
invalidateInternal()方法詳情:其實關(guān)鍵的方法就是invalidateChild()void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { if (mGhostView != null) { mGhostView.invalidate(true); return; } // 判斷是否可見,是否在動畫中,是否不是ViewGroup,三項滿足一項,直接返回 if (skipInvalidate()) { return; } //根據(jù)View的標記位來判斷該子View是否需要重繪,假如View沒有任何變化,那么就不需要重繪 if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)) { if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; } //設(shè)置PFLAG_DIRTY標記位 mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } //把需要重繪的區(qū)域傳遞給父容器 // Propagate the damage rectangle to the parent view. final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); //調(diào)用父容器的方法,向上傳遞事件 p.invalidateChild(this, damage); } // Damage the entire projection receiver, if necessary. // 損壞整個投影接收機,如果不需要。 if (mBackground != null && mBackground.isProjected()) { final View receiver = getProjectionReceiver(); if (receiver != null) { receiver.damageInParent(); } } } }- 1、判斷是否可見,是否在動畫中,是否不是
ViewGroup,三項滿足一項,直接返回,這個方法也可以知道,invalidate()針對的是View,而不是ViewGroup
private boolean skipInvalidate() { return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null && (!(mParent instanceof ViewGroup) || !((ViewGroup) mParent).isViewTransitioning(this)); }- 2、通過View的標記位來判斷孩子View是否需要重新繪制,如果沒有變化的話,那么就不需要重新繪制
mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)- 3、需要重新繪制的區(qū)域傳遞給父容器,向上傳遞事件,記住在這里
damage這個變量肯定不為null,要不然在這個方法里面就會直接拋出空指針異常。
p.invalidateChild(this, damage);- 4、損壞整個投影接收機,如果不需要。
mBackground.isProjected(): 這張畫是否需要投影。
if (mBackground != null && mBackground.isProjected()) { final View receiver = getProjectionReceiver(); if (receiver != null) { receiver.damageInParent(); } } - 1、判斷是否可見,是否在動畫中,是否不是
- 關(guān)鍵的方法:
ViewRootImpl.invalidateChild(this, damage);
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
-
invalidateChildInParent(null, dirty);進行了offset和union對坐標的調(diào)整,然后把dirty區(qū)域的信息保存在mDirty中,最后調(diào)用了
scheduleTraversals()方法,觸發(fā)View的工作流程,由于沒有添加measure和layout的標記位,因此measure、layout流程不會執(zhí)行,而是直接從draw流程開始.@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { // 檢查線程,不是ui線程,直接拋出異常 checkThread(); if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); if (dirty == null) { invalidate(); return null; } else if (dirty.isEmpty() && !mIsAnimating) { return null; } if (mCurScrollY != 0 || mTranslator != null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY != 0) { // 將dirty中的坐標轉(zhuǎn)化為父容器中的坐標,考慮mScrollX和mScrollY的影響 dirty.offset(0, -mCurScrollY); } if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } } //進行了offset和union對坐標的調(diào)整 invalidateRectOnScreen(dirty); return null; }- 1、檢查線程,不是ui線程,直接拋出異常.和
requestLayout()一樣的
checkThread();- 2、如果是從 invalidate() 方法過來的話,那么dirty 肯定不為null 因為要是為null的話,前面調(diào)用方法的地方就拋出了空指針的異常
if (dirty == null) { invalidate(); return null; }- 3、通過將dx=0添加到其左、右坐標,并將 mCurScrollY 添加到其頂部和底部坐標來抵消矩形。
dirty.offset(0, -mCurScrollY);- 4、進行了offset和union對坐標的調(diào)整
invalidateRectOnScreen(dirty); - 1、檢查線程,不是ui線程,直接拋出異常.和
- 關(guān)于
invalidateRectOnScreen(dirty)方法:最終的關(guān)鍵的方法: scheduleTraversals();
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
// Add the new dirty rect to the current one
// 添加一個新的 dirty rect 給當(dāng)前的Rect
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
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();
}
}
- 最終的關(guān)鍵的方法:
ViewRootImpl.scheduleTraversals();,也就是會調(diào)用到這個對象mTraversalRunnable;也就是和requessLaout()最終調(diào)用的底層的方法一樣,只不過對于invalidate()沒有添加measure()和layout()的標記位,后面的流程也就不會執(zhí)行!具體的流程可以看這篇文章Android源碼分析(View的繪制流程) - 該方法的調(diào)用會引起
View樹的重繪,常用于內(nèi)部調(diào)用(比如 setVisiblity())或者需要刷新界面的時候,需要在主線程(即UI線程)中調(diào)用該方法,invalidate有多個重載方法,但最終都會調(diào)用invalidateInternal方法,在這個方法內(nèi)部,進行了一系列的判斷,判斷View是否需要重繪,接著為該View設(shè)置標記位,然后把需要重繪的區(qū)域傳遞給父容器,即調(diào)用父容器的invalidateChild方法。 -
做了一張圖
invalidate()的原理.jpg
postInvalidate()的源碼解析
-
view.postInvalidate()繼承一個Textview,然后重寫方法,設(shè)置一個but,同時請求方法,打印日志:請求一次的輸出的結(jié)果
InvalidateTextView----------draw
InvalidateTextView------------onDraw
-
view.postInvalidate()詳情,由于方法是public,也可以調(diào)用一個時間,延遲多久開始執(zhí)行,這里是delayMilliseconds,毫秒
public void postInvalidate() {
postInvalidateDelayed(0);
}
-
view.postInvalidateDelayed()只有attachInfo不為null的時候才會繼續(xù)執(zhí)行,即只有確保視圖被添加到窗口的時候才會通知view樹重繪,因為這是一個異步方法,如果在視圖還未被添加到窗口就通知重繪的話會出現(xiàn)錯誤,所以這樣要做一下判斷!
public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
-
ViewRootImpl.dispatchInvalidateDelayed()用了Handler,發(fā)送了一個異步消息到主線程,顯然這里發(fā)送的是MSG_INVALIDATE,即通知主線程刷新視圖
/**
* 用了Handler,發(fā)送了一個異步消息到主線程,顯然這里發(fā)送的是`MSG_INVALIDATE`,即通知主線程刷新視圖
* @param view 只有 postInvalidate() 使用了handler 來發(fā)送消息
* @param delayMilliseconds
*/
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
- 通知對象去
invalidate(),底層也是調(diào)用的是invalidate(),只不過使用了mHandler發(fā)送消息,在這里就發(fā)送到主線程了,去調(diào)用invalidate()方法
case MSG_INVALIDATE:
//通知對象去 invalidate ,底層也是調(diào)用的是 invalidate,只不過使用了handler發(fā)送消息
((View) msg.obj).invalidate();
break;0
方法的解釋 :
postInvalidate是在非UI線程中調(diào)用,但是底層使用的是invalidate(),通過ViewRootImpl的內(nèi)部handler: ViewRootHandler發(fā)送的消息,但是也可以在 主線程中使用,如果在強制在主線程中使用,內(nèi)部有個handler在工作,是不是顯得有點浪費 ,對吧!postInvalidate()這個方法也可以主線程中使用-
做了一張圖
postInvalidate()的原理.jpg -
最后說明幾點
-
invalidate()、postInvalidate()、requestLayout(),最底層處調(diào)用的是viewRootImpl.scheduleTraversals()這個方法,requestLayout由于設(shè)置了measure和layout的標記位,所以requestLayout可以重新走一次繪制的流程 -
postInvalidate()底層通過Handler把非UI線程的工作,調(diào)用的是invalidate(). -
invalidate()、requestLayout(),方法都檢查了是否在UI線程,不在的話,直接拋出異常,所以他們只能在UI線程中使用,postInvalidate()可以在UI線程和非UI線程中使用。 -
view自身不在適合某個區(qū)域,比如說``LayoutParams發(fā)生了改變,需要對其重新的測量、布局和繪制的三個流程,那么使用這個方法最合適requestLayout()`。 - 如果說是在刷新當(dāng)前的view,是當(dāng)前的view進行重新的繪制,不會進行測量、布局流程。僅僅需要某個
view重新繪制而不需要測量,使用invalidate()方法往往比requestLayout()方法更高效
-


