安卓invalidate()、postInvalidate()、req
最近在擼
Golang
有點上火了,來整理下安卓原始碼資料分析結果基於
Audroid API 26
requestLayout()
原始碼分析
假如在一個頁面上有個按鈕,點選按鈕就對一個
view.requestLayout()
,這個view
執行的方法如下:
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) { //只有在佈局邏輯中觸發請求,如果這是請求它的檢視,而不是其父層次結構中的檢視 ViewRootImpl viewRoot = getViewRootImpl(); //如果連續請求兩次,其中一次自動返回! if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } //todo 為當前view設定標記位 PFLAG_FORCE_LAYOUT mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { // todo 向父容器請求佈局 這裡是向父容器請求佈局,即呼叫父容器的requestLayout方法,為父容器新增PFLAG_FORCE_LAYOUT標記位,而父容器又會呼叫它的父容器的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
,如果是,就返回,也就可以理解為: 如果連續請求兩次,並且其中的一次正在layout
中,其中一次返回!這樣做是節約效能
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { //只有在佈局邏輯中觸發請求,如果這是請求它的檢視,而不是其父層次結構中的檢視 ViewRootImpl viewRoot = getViewRootImpl(); //如果連續請求兩次,其中一次自動返回! if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; }
3、 為當前
view
設定標記位PFLAG_FORCE_LAYOUT
,關於|=
符號:a|=b
的意思就是把a和b按位或然後賦值給a 按位或的意思就是先把a和b都換成2進位制,然後用或操作,相當於a=a|b
mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED;
4、向父容器請求佈局,即呼叫
ViewGroup
父容器的requestLayout()
方法,為父容器新增PFLAG_FORCE_LAYOUT
標記位,而父容器又會呼叫它的父容器的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(); } }
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
// requestLayout() 會呼叫這個方法 void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 最終呼叫的是這個方法 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
2 、最終走到這個方法來
ViewRootImpl.scheduleTraversals()
,在其中可以看到一行非常有意思的程式碼mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
,其中有個物件mTraversalRunnable
,這樣下去就會重新的測量、佈局和繪製;具體的流程可以看這篇文章
1、檢查是否在主執行緒,不在的話,丟擲異常
checkThread();
有個問題,我先丟擲結論,
requessLayout() 、invalidate()、postInvalidate()
最終的底層呼叫的是ViewRootImpl.scheduleTraversals()
的方法,為什麼僅僅requessLayout()
才會執行onMeasure() onLayout() onDraw()
這幾個方法?關於
view.measure()
方法:在前面我們知道mPrivateFlags |= PFLAG_FORCE_LAYOUT
所以forceLayout = true
,也就是會執行onMeasure(widthMeasureSpec, heightMeasureSpec);
,同時執行完了以後呢,最後為標記位設定為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; } // 接著最後為標記位設定為PFLAG_LAYOUT_REQUIRED mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } ... }
關於
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) { //第二次呼叫這個方法,,, 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()
的分析的結果:view
呼叫了這個方法,其實會從view
樹重新進行一次測量、佈局、繪製這三個流程。-
做了一張圖
requestLayout()的原理.jpg
invalidate()原始碼分析
view.invalidate()
;繼承一個Textview,然後重寫方法,設定一個but,同時請求方法,列印日誌:請求一次的輸出的結果
InvalidateTextView----------draw InvalidateTextView------------onDraw
方法詳情 :
view.invalidate()
public void invalidate() { invalidate(true); }
該檢視的繪圖快取是否也應無效。對於完全無效,設定為true,但是如果檢視的內容或維度沒有改變,則可以設定為false。
public void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); }
-
invalidateInternal()
方法詳情:其實關鍵的方法就是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; }//根據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; } //設定PFLAG_DIRTY標記位 mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } //把需要重繪的區域傳遞給父容器 // 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); //呼叫父容器的方法,向上傳遞事件 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(); } } } }
作者:豌豆射手_BiuBiu
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2480/viewspace-2817445/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android自定義View之invalidate方法和postInvalidate方法AndroidView
- View—requestLayout、invalidate 和 postInvalidate 三者的區別View
- Android中invalidateAndroid
- 安卓安卓
- 看雪安卓研修班,安卓逆向安卓
- 安卓 no_proxy安卓
- 安卓開發第一步:安卓面試題安卓面試題
- 看雪安卓容器安卓
- 安卓四種引用安卓
- 安卓動畫(一)安卓動畫
- 安卓關閉fragment安卓Fragment
- 安卓遊戲大全安卓遊戲
- 安卓09作業安卓
- 安卓EasyLib的使用安卓
- createContext(req, res)幹了什麼?Context
- 安卓開發:安卓底部選單欄的實現,RadioGroup 和Fragment安卓Fragment
- 恢復安卓谷歌套件安卓谷歌套件
- 安卓防簽名策略安卓
- 安卓架構文章合集安卓架構
- 安卓元件化化落地安卓元件化
- 安卓音樂頻譜安卓
- 安卓真機除錯安卓除錯
- Unity安卓共享紋理Unity安卓
- 安卓快速關機APP安卓APP
- 安卓圖表引擎AChartEngine安卓
- 安卓系統架構安卓架構
- 安卓開發--AIDL研究安卓AI
- 安卓Toolbar使用 Demo(Kotlin)安卓Kotlin
- 安卓部署Java專案安卓Java
- JUSTCTF校賽安卓wp安卓
- 安卓 TV 怎麼安裝證書?安卓
- 【安卓漏洞挖掘】drozer--安卓四大元件的漏洞學習安卓元件
- 使用fiddler和安卓模擬器抓取安卓客戶端資料包安卓客戶端
- [20210305]Oracle Rolling Invalidate Window Exceeded(3).txtOracle
- 華為Mate 8將升級安卓8.0:嚐遍3個安卓大版本安卓
- 安卓學習資源整理安卓
- 安卓 VS iOS,誰更安全?安卓iOS
- 安卓的切圖規範安卓