invalidate方法知多少[-View-] 源碼級

零、前言

本文聚焦:
[1].View#invalidate作了什麼,爲何會觸發View的重繪?
[2].View是如何被添加到ViewGroup中的?
[3].ViewGroup和ViewRootImpl在invalidate孩子上作了什麼?
[4].源碼的層次上分析invalidate和postInvalidate的差別與聯繫?
複製代碼

1.View#invalidate方法

View刷新.png

---->[View#invalidate]--------------------
public void invalidate() {
    invalidate(true);
}

---->[View#invalidate(boolean)]--------------------
 * @hide
 */
public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

---->[View#invalidateInternal]--------------------
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }
    ...
        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);
            ///調用父控件的invalidateChild()刷新本View
            p.invalidateChild(this, damage);
        }
        ...
    }
}
|--- 到這裏暫停一下
這mGhostView真跟鬼同樣,View中出現了8次,竟沒有一次對它賦值  
因爲是包訪問的可能在其餘類裏吧,這裏注意一下,畢竟若是他不爲空,就畫他而後return複製代碼

2.誰是我爸?View的滴血認親

整個View中並無ViewGroup的身影,而是依靠接口[ViewParent]全權負責
這有一個問題:ViewParent的實現類是誰? 明面有一個ViewGroup的實現, 但別忘了幕後還有個大佬ViewRootImpl也是實現了ViewParent的,那這個p究竟是誰呢?git

ViewGroup添加View.png

|--能夠看到p是承接mParent的局部變量,全文搜索[mParent =]來查看他什麼時候初始化或被賦值的
---->[View#成員變量]-----------------------------
//The parent this view is attached to. -- 該View添加到的父View
protected ViewParent mParent;  //注意是protected的訪問權限

---->[View#assignParent]-----------------------------
|-- 這裏可見assignParent是初始化mParent的核心方法
void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent");
    }
}
|-- 搜索了一下assignParent在View中並未被調用,那隻能說是別人調的  
|-- 和View認老爸關係最密切的當屬ViewGroup中的addView了,來看一下

---->[ViewGroup#addView(View)]-----------------------------
public void addView(View child) {
--->addView(child, -1);
}

---->[ViewGroup#addView(View,int)]-----------------------------
public void addView(View child, int index) {
    ...
    LayoutParams params = child.getLayoutParams();
    if (params == null) {
        params = generateDefaultLayoutParams();
        ...
    }
--->addView(child, index, params);
}

---->[ViewGroup#addView(View,int,LayoutParams)]-----------------------------
public void addView(View child, int index, LayoutParams params) {
    ...
    requestLayout();
    invalidate(true);
--->addViewInner(child, index, params, false);
}

---->[ViewGroup#addViewInner]-----------------------------
private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {
    ...
--->addInArray(child, index);//將孩子加入到本身的數組裏
    // tell our children -- 告訴咱們的孩子們,他們有爹了
    if (preventRequestLayout) {
--->    child.assignParent(this);// 即是咱們要尋的
    } else {
--->    child.mParent = this; //這直接讓孩子的mParent賦值
    }
    ...
}

|-- 如今再看一下ViewRootImpl,我就單刀直入了,從setView開始,不懂的,看前面幾篇相關內容
---->[ViewRootImpl#setView]-------------------------
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ... view.assignParent(this);
}

|-- 此時的View前幾篇分析過是DecorView,其中調用了DecorView的assignParent,
因此DecorView是認ViewRootImpl爲老爹的,雖然ViewRootImpl不是View,但它倒是是個ViewParent
因此當爹是沒問題的,那麼View的invalidate方法走的是ViewGroup仍是ViewRootImpl的invalidateChild?

答:若是是一個ViewGroup,它添加了子View,該子View的爹就是ViewGroup,
走的固然也是ViewGroup#invalidateChild,這是咱們平常開發中最多見的

但對於最頂層的DecorView,誰敢當他爹?ViewRootImpl就是他老爸,因此對於DecorView的invalidate方法
固然走的是ViewRootImpl#invalidateChild,因此這就是爲何ViewRootImpl爲何那麼厲害的緣由
換句話來講,協天子以令諸侯有沒有。ViewRootImpl說大家不要在子線程給我刷新UI,View們就乖乖照作
複製代碼

3.ViewGroup#invalidateChild方法
---->[ViewGroup#invalidateChild]--------------------
|--- ViewGroup做爲ViewParent的實現類, invalidateChild方法咱們看到了
public final void invalidateChild(View child, final Rect dirty) {
    ...
    ViewParent parent = this;
    if (attachInfo != null) {
        ...
        }
        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }
            ...
            //循環找到根view,並調用invalidateChildInParent()方法
            parent = parent.invalidateChildInParent(location, dirty);
            if (view != null) {
                ...
            }
        } while (parent != null);
    }
}

|-- 這裏經過 while 來遍歷 this ,都執行了一個invalidateChildInParent的方法  
該方法返回了一個ViewParent對象,來看一下這個方法:

---->[ViewGroup#invalidateChildInParent]--------------------
public ViewParent invalidateChildInParent(final int[] location, final Rect dir
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
        ...
        return mParent; 
    }
    return null;
}
|-- 這方法看起來只是設置了一下本身的區域和擺位,並無什麼實質性的東西  
|-- 不過亮點是他的返回值mParent,也就是它把本身整理一下,把老爸跑出去了  
|-- 這樣看來上面的invalidateChild就是一直拋老爸,直到DecorView  
|-- 由於DecorView 的老爸是ViewRootImpl,因此[parent instanceof View]的條件不知足  
|-- 這時候就調用了ViewRootImpl#invalidateChild(ViewGroup全程打醬油的既視感...)

複製代碼

4.繪製更新核心:ViewRootImpl#invalidateChild方法

ViewGroup並不給力,並無觸發孩子繪製方法,ViewRootImpl大佬出場,一招定乾坤github

ViewRootImpl刷新孩子.png

---->[ViewRootImpl#invalidateChild]--------------------
 @Override
 public void invalidateChild(View child, Rect dirty) {
     invalidateChildInParent(null, dirty);
 }
 
---->[ViewRootImpl#invalidateChildInParent]--------------------
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();//劃重點...這裏檢查線程。曹操說:子線程不能更新UI
    if (dirty == null) {
        invalidate();
        return null;
    } else if (dirty.isEmpty() && !mIsAnimating) {
        return null;
    }
    ...
--->invalidateRectOnScreen(dirty);
    return null;
}

---->[ViewRootImpl#invalidateRectOnScreen]--------------------
private void invalidateRectOnScreen(Rect dirty) {
    ...
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
--->    scheduleTraversals();//開啓了一個遍歷的計劃
    }
}


---->[ViewRootImpl#scheduleTraversals]--------------------
|---Choreographer 翻譯一下:舞蹈指導者?--大佬真會起名字...
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
    --->        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

--------下一段不想看能夠跳過,主要追了一下傳入的Runnable是何時被執行的-------
|---Choreographer的postCallback核心調用的是下面的這個方法:
|--- 主要看入參Runnable的去向,下面的action即是Runnable
---->[Choreographer##postCallbackDelayedInternal]--------------------
private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    ...
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //這裏對action作了處理
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

public void addCallbackLocked(long dueTime, Object action, Object token) {
    CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
    ...
}

private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
    CallbackRecord callback = mCallbackPool;
    if (callback == null) {
        callback = new CallbackRecord();
    } else {
        mCallbackPool = callback.next;
        callback.next = null;
    }
    callback.dueTime = dueTime;
--->callback.action = action;
    callback.token = token;
    return callback;
}
|-- 可見action流轉到了CallbackRecord的action字段中了

---->[Choreographer#CallbackRecord]------------------------------------------
|-- 可見CallbackRecord的run方法觸發了action的run
private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;

    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            ((Runnable)action).run();
        }
    }
}

|-- 全局搜了一下,代碼就不貼了,最後[c.run(frameTimeNanos)]在[doCallbacks]方法中觸發  
|-- 而[doCallbacks]在[doFrame]觸發,[doFrame]在handler接收[MSG_DO_FRAME]時觸發  
---------------------------------------------------------------------------------------

|--言歸正傳:mTraversalRunnable是一個Runnable,經過Choreographer#postCallback最終會被執行
|-- 看一下mTraversalRunnable是什麼,幹了啥
---->[Choreographer#CallbackRecord]--------------------------------------
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

|-- 簡單易懂,執行doTraversal()操做,至於doTraversal()是幹嗎的...
|-- 簡單講一下,doTraversal()操做遍歷全部節點,進行測量、佈局、繪製--(這曹操當得也不容易啊)
|-- 一樣的分析我不想寫第二遍,詳見:所得與所見:[-View周邊-] 框架層#三#4 
複製代碼

到這裏總算解開我:invalidate怎樣觸發View重繪的謎題了。編程


5.postInvalidate()和 invalidate的區別

postInvalidate分析.png

---->[View#postInvalidate]-----------------------
public void postInvalidate() {
    postInvalidateDelayed(0);
}

---->[View#postInvalidateDelayed]-----------------------
public void postInvalidateDelayed(long delayMilliseconds) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}
|-- 感受挺直爽,直接拿ViewRootImpl#dispatchInvalidateDelayed

---->[ViewRootImpl#dispatchInvalidateDelayed]-----------------------
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
|-- Handler經過obtainMessage將view放在一個MSG_INVALIDATE標識的Message中
|-- 若是Handler不熟悉的,還請移駕:Android點將臺:烽火狼煙[-Handler-]
|-- 看Handler,首先不是看它的handleMessage是怎麼處理的,而是看Handler在哪一個線程建立的  
|-- 也就是Handler的Looper是在哪一個線程。

---->[ViewRootImpl#mHandler]-----------------------
|-- 並無在子線程,加上ViewRootImpl是在主線程被建立的(不知道到的看前文),因此mHandler是主線程
final ViewRootHandler mHandler = new ViewRootHandler();

 @Override
 public void handleMessage(Message msg) {
     switch (msg.what) {
     case MSG_INVALIDATE:
         ((View) msg.obj).invalidate();//調用了View的invalidate,已切至主線程
         break;
         
|-- 到這裏就到頭了,也就是說谷歌的大佬怕咱們在子線程invalidate煩神
|-- 就內置的了一個Handler幫咱們省去麻煩,至於用invalidate仍是postInvalidate?

一條直線能到家,你還非要拐個彎嗎?畢竟postInvalidate也是觸發了View#invalidate 
還要額外發個消息才能玩。因此主線程用invalidate,在子線程能夠用postInvalidate  
固然你以爲postInvalidate太長很差看,能夠也無視大佬的一片好心,本身新建Handler,只要你開心...
複製代碼

總的看來,View的invalidate方法也並無我相信中的那麼複雜,半天就寫完了...數組


後記:捷文規範

1.本文成長記錄及勘誤表
項目源碼 日期 附錄
V0.1-- 2018-2-23

發佈名:invalidate方法知多少[-View-] 源碼級
捷文連接:juejin.im/post/5c70ce…bash

2.更多關於我
筆名 QQ 微信
張風捷特烈 1981462002 zdl1994328

個人github:github.com/toly1994328
個人簡書:www.jianshu.com/u/e4e52c116…
個人掘金:juejin.im/user/5b42c0…
我的網站:www.toly1994.com微信

3.聲明

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持框架

icon_wx_200.png
相關文章
相關標籤/搜索