安卓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中Invalidate和postInvalidate的區別Android
- Android自定義View之invalidate方法和postInvalidate方法AndroidView
- View—requestLayout、invalidate 和 postInvalidate 三者的區別View
- android-invalidate和postInvalidate 的區別及使用Android
- Android筆記:invalidate()和postInvalidate() 的區別及使用——重新整理uiAndroid筆記UI
- node.js取參四種方法req.body,req.params,req.param,req.bodyNode.js
- Session invalidateSession
- Android中invalidateAndroid
- informix CKPT REQ 狀態處理!ORM
- createContext(req, res)幹了什麼?Context
- [20120829]分析表與no_invalidate=AUTO_INVALIDATE.txt
- oracle dbms_stats(no_invalidate引數)Oracle
- OpenSSL 證書請求和自簽名命令 req 詳解
- cache操作:clean、invalidate與flush的含義
- Android view中的requestLayout和invalidate方法AndroidView
- Oracle收集統計資訊之NO_INVALIDATE引數Oracle
- Oracle 統計量NO_INVALIDATE引數配置(下)Oracle
- Oracle 統計量NO_INVALIDATE引數配置(上)Oracle
- 請問session.invalidate()前是否需要removeAttribute()SessionREM
- pytest-req外掛:更簡單的做介面測試
- Android自定義View之requestLayout方法和invalidate方法AndroidView
- ASM DG Usable_file_MB和Req_mir_free_MB的含義ASM
- 解決WiFi韌體編譯錯誤:STATION_INFO_ASSOC_REQ_IESWiFi編譯
- 安卓安卓
- Android學習之 圖解呼叫invalidate()和requestLayout()的過程Android圖解
- 看雪安卓研修班,安卓逆向安卓
- 微信授權報code been used, hints: [ req_id: XYv1Ha07042046 ]
- 安卓 listview安卓View
- 你需要了解下Android View的更新requestLayout與重繪invalidateAndroidView
- Rolling Cursor Invalidations with DBMS_STATS.AUTO_INVALIDATE (文件 ID 557661.1)
- Android draw、onDraw、dispatchDraw、invalidate、computeScroll 一些簡要說明Android
- 安卓編年史(31):安卓 6.0 棉花糖安卓
- 華為雲釋出CodeArts Req需求管理工具,讓需求管理化繁為簡
- [Android] Qt安卓教程(2):移植Qt到安卓AndroidQT安卓
- [20210305]Oracle Rolling Invalidate Window Exceeded(3).txtOracle
- MFC幾個常用函式:OnCreate和OnInitialUpDate,GetActiveFrame和MDIGetActive,Invalidate、SetModifiedFlage、UpdateAll函式
- 安卓動畫(一)安卓動畫
- 安卓鬧鐘安卓