1. 程式人生 > >View之invalidate,requestLayout,postInvalidate

View之invalidate,requestLayout,postInvalidate

標記 per isl its 維護 child batch out 發生

目錄介紹

  • 01.invalidate,requestLayout,postInvalidate區別
  • 02.invalidate深入分析
  • 03.postInvalidate深入分析
  • 04.requestLayout深入分析
  • 05.ViewRootImpl作用分析
  • 06.這幾個方法總結

好消息

  • 博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護並且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!
  • 01.Java基礎問題(19個)
  • 02.Java面向對象問題(10個)
  • 03.Java數據結構問題(20個)
  • 04.JavaIO流問題(6個)
  • 05.java多線程問題(19)
  • 06.Java虛擬機問題(10個)
  • 07.Java類加載問題(8個)
  • 08.Java反射問題(6個)
  • 10.Java異常問題(9個)

01.requestLayout、invalidate與postInvalidate作用與區別

  • invalidate() postInvalidate()
    • 共同點:都是調用onDraw()方法,然後去達到重繪view的目的
    • 區別:invalidate()用於主線程,postInvalidate()用於子線程【通過handler發送消息,在handleMessage中((View) msg.obj).invalidate(),】
  • requestLayout()
    • 也可以達到重繪view的目的,但是與前兩者不同,它會先調用onLayout()重新排版,再調用ondraw()方法。
    • 當view確定自身已經不再適合現有的區域時,該view本身調用這個方法要求parent view(父類的視圖)重新調用他的onMeasure、onLayout來重新設置自己位置。特別是當view的layoutparameter發生改變,並且它的值還沒能應用到view上時,這時候適合調用這個方法requestLayout()。 requestLayout調用onMeasure和onLayout,不一定調用onDraw

02.invalidate深入分析

  • 看看invalidate源碼,如下所示
    • invalidateCache為true表示全部重繪。View的invalidate方法最後調用的是invalidateInternal方法,invalidateInternal方法中的重點邏輯在源碼上添加註釋呢。
    public void invalidate() {
        invalidate(true);
    }
    
    
    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }
    
    
    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        if (mGhostView != null) {
            mGhostView.invalidate(true);
            return;
        }
    
        if (skipInvalidate()) {
            return;
        }
    
        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;
            }
    
            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();
                }
            }
        }
    }
    
  • 看看ViewGroup中的invalidateChild方法
    • 在ViewGroup的invalidateChild方法中有一個循環,循環裏面會一直調用父布局的invalidateChildInParent方法,而View和ViewGroup的最終父布局都是ViewRootImpl。
    @UiThread
    public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    
        @Override
        public final void invalidateChild(View child, final Rect dirty) {
            ViewParent parent = this;
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                //這是一個從當前的布局View向上不斷遍歷當前布局View的父布局,最後遍歷到ViewRootImpl的循環
                do {
                    View view = null;
                    if (parent instanceof View) {
                        view = (View) parent;
                    }
                    
                    //這裏調用的是父布局的invalidateChildInParent方法
                    parent = parent.invalidateChildInParent(location, dirty);
                } while (parent != null);
            }
        }
        
        @Override
        public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
            if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
                    (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
                if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                            FLAG_OPTIMIZE_INVALIDATE) {
                    ...
                    //這裏也是一些計算繪制區域的內容
                    return mParent;
                } else {
                    mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
                    //這裏也是一些計算繪制區域的內容
                    return mParent;
                }
            }
            return null;
        }
    }
    
  • View中的invalidateChild方法和ViewGroup中的invalidateChildInParent方法最後殊途同歸,都會調用到ViewRootImpl中的方法
    • 可以看到在ViewRootImpl中最後都會調用scheduleTraversals方法進行繪制。按照對於View的繪制過程的理解,View的繪制流程是從ViewRootImpl的performTraversals方法開始的
    public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
        
        //如果View沒有父布局,那invalidateInternal方法就會調用這個方法
        @Override
        public void invalidateChild(View child, Rect dirty) {
            invalidateChildInParent(null, dirty);
        }
    
        //ViewGroup的invalidateChild方法最後會調用到這裏
        @Override
        public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
            checkThread();
            //如果dirty為null就表示要重繪當前ViewRootImpl指示的整個區域
            if (dirty == null) {
                invalidate();
                return null;
            //如果dirty為empty則表示經過計算需要重繪的區域不需要繪制
            } else if (dirty.isEmpty() && !mIsAnimating) {
                return null;
            }
            return null;
        }   
        
        private void invalidateRectOnScreen(Rect dirty) {
            final Rect localDirty = mDirty;
            ...
            if (!mWillDrawSoon && (intersected || mIsAnimating)) {
                //調用scheduleTraversals方法進行繪制
                scheduleTraversals();
            }
        }
        
        //繪制整個ViewRootImpl區域
        void invalidate() {
            mDirty.set(0, 0, mWidth, mHeight);
            if (!mWillDrawSoon) {
                //調用scheduleTraversals方法進行繪制
                scheduleTraversals();
            }
        }
    }
    
  • 下面我們來看看ViewRootImpl中的scheduleTraversals方法
    • 看到scheduleTraversals方法中調用了mChoreographer.postCallback方法
    • Choreoprapher類的作用是編排輸入事件、動畫事件和繪制事件的執行,它的postCallback方法的作用就是將要執行的事件放入內部的一個隊列中,最後會執行傳入的Runnable,這裏也就是mTraversalRunnable。
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
  • 來看看TraversalRunnable這個類做了什麽?
    • 可以看到最終調用了performTraversals()方法進行繪制
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
    
            performTraversals();
    
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
    
  • 大概總結一下
    • invalidate方法最終調用的是ViewRootImpl中的performTraversals來重新繪制View
    • 在自定義View時,當需要刷新View時,如果是在UI線程中,那就直接調用invalidate方法,如果是在非UI線程中,那就通過postInvalidate方法來刷新View

03.postInvalidate深入分析

  • 先來看看View中的postInvalidate方法
    @UiThread
    public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
        
        ...
        
        public void postInvalidate() {
            postInvalidateDelayed(0);
        }
        
        public void postInvalidate(int left, int top, int right, int bottom) {
            postInvalidateDelayed(0, left, top, right, bottom);
        }
        
        public void postInvalidateDelayed(long delayMilliseconds) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
            }
        }
        ...      
    }
    
  • 可以看到,postInvalidate方法最後調用了ViewRootImpl的dispatchInvalidateDelayed方法
    • ViewRootImpl中的dispatchInvalidateDelayed方法就是像ViewRootHandler發送了一個MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一個內部Handler類
    //發送消息
    //更多內容:https://github.com/yangchong211
    public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }
    
    //接收消息
    final class ViewRootHandler extends Handler {
        @Override
        public String getMessageName(Message message) {
            switch (message.what) {
                case MSG_INVALIDATE:
                    return "MSG_INVALIDATE";
            }
            return super.getMessageName(message);
        }
    
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_INVALIDATE:
                ((View) msg.obj).invalidate();
                break;
            }
        }
    }
    
  • 大概總結一下
    • postInvalidate方法調用了ViewRootImpl中的dispatchInvalidateDelayed方法向ViewRootImpl中的ViewRootHandler發送一個消息,最後調用的還是View的invalidate方法。
    • 因為ViewRootImpl是在UI線程的,所以postInvalidate方法的作用就是將非UI線程的刷新操作切換到UI線程,以便在UI線程中調用invalidate方法刷新View。所以如果我們自定義的View本身就是在UI線程,沒有用到多線程的話,直接用invalidate方法來刷新View就可以了。而我們平時自定義View基本上都沒有開起其他線程,所以這就是我們很少接觸postInvalidate方法的原因。
    • 一句話總結postInvalidate方法的作用就是:實現了消息機制,可以使我們在非UI線程也能調用刷新View的方法。

04.requestLayout深入分析

  • 源碼如下所示
    • 在View的requestLayout方法中,首先會設置View的標記位,PFLAG_FORCE_LAYOUT表示當前View要進行重新布局,PFLAG_INVALIDATED表示要進行重新繪制。
    • requestLayout方法中會一層層向上調用父布局的requestLayout方法,設置PFLAG_FORCE_LAYOUT標記,最終調用的是ViewRootImpl中的requestLayout方法。
    //View.class
    @CallSuper
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();
    
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
    
        //設置PFLAG_FORCE_LAYOUT標記位,這樣就會導致重新測量和布局
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        //設置PFLAG_INVALIDATED就會進行重新繪制
        mPrivateFlags |= PFLAG_INVALIDATED;
    
        if (mParent != null && !mParent.isLayoutRequested()) {
            //不斷調用上層View的requestLayout方法
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
    
  • 然後看看ViewRootImpl中的requestLayout方法
    • 可以看到ViewRootImpl中的requestLayout方法中會調用scheduleTraversals方法,scheduleTraversals方法最後會調用performTraversals方法開始執行View的三大流程,會分別調用View的measure、layout、draw方法。
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
  • 然後再看看measure測量方法
    • 由於requestLayout方法設置了PFLAG_FORCE_LAYOUT標記位,所以measure方法就會調用onMeasure方法對View進行重新測量。在measure方法中最後設置了PFLAG_LAYOUT_REQUIRED標記位,這樣在layout方法中就會執行onLayout方法進行布局流程。
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //調用onMeasure方法
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            //設置PFLAG_LAYOUT_REQUIRED標記位,用於layout方法
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
    }
    
  • 再然後看看layout方法
    • 由於measure方法中設置了PFLAG_LAYOUT_REQUIRED標記位,所以在layout方法中onLayout方法會被調用執行布局流程。最後清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED標記位。
    public void layout(int l, int t, int r, int b) {
        //由於measure方法中設置了PFLAG_LAYOUT_REQUIRED標記位,所以會進入調用onLayout方法進行布局流程
        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;
            }
    
            //取消PFLAG_LAYOUT_REQUIRED標記位
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
    
        //取消PFLAG_FORCE_LAYOUT標記位
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }
    

05.ViewRootImpl作用分析

  • 鏈接WindowManager和DecorView的紐帶,另外View的繪制也是通過ViewRootImpl來完成的。
    • 它的主要作用我的總結為如下:
    • A:鏈接WindowManager和DecorView的紐帶,更廣一點可以說是Window和View之間的紐帶。
    • B:完成View的繪制過程,包括measure、layout、draw過程。
    • C:向DecorView分發收到的用戶發起的event事件,如按鍵,觸屏等事件。

06.這幾個方法總結

  • requestLayout方法會標記PFLAG_FORCE_LAYOUT,然後一層層往上調用父布局的requestLayout方法並標記PFLAG_FORCE_LAYOUT,最後調用ViewRootImpl中的requestLayout方法開始View的三大流程,然後被標記的View就會進行測量、布局和繪制流程,調用的方法為onMeasure、onLayout和onDraw。
  • invalidate方法我們分析過,它的過程和requestLayout方法方法很像,但是invalidate方法沒有標記PFLAG_FORCE_LAYOUT,所以不會執行測量和布局流程,而只是對需要重繪的View進行重繪,也就是只會調用onDraw方法,不會調用onMeasure和onLayout方法。

其他介紹

01.關於博客匯總鏈接

  • 1.技術博客匯總
  • 2.開源項目匯總
  • 3.生活博客匯總
  • 4.喜馬拉雅音頻匯總
  • 5.其他匯總

02.關於我的博客

  • 我的個人站點:www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yczbj/activities
  • 簡書:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
  • 開源中國:https://my.oschina.net/zbj1618/blog
  • 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 郵箱:[email protected]
  • 阿裏雲博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
  • 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e

View之invalidate,requestLayout,postInvalidate