安卓invalidate()、postInvalidate()、req

wh7577發表於2021-09-09
  • 最近在擼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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章