Android 之 ViewTreeObserver 全面解析

UI 優(yōu)化系列專題,來聊一聊 Android 渲染相關(guān)知識(shí),主要涉及 UI 渲染背景知識(shí)如何優(yōu)化 UI 渲染兩部分內(nèi)容。


UI 優(yōu)化系列專題
  • UI 渲染背景知識(shí)

View 繪制流程之 setContentView() 到底做了什么?
View 繪制流程之 DecorView 添加至窗口的過程
深入 Activity 三部曲(3)View 繪制流程
Android 之 LayoutInflater 全面解析
關(guān)于渲染,你需要了解什么?
Android 之 Choreographer 詳細(xì)分析

  • 如何優(yōu)化 UI 渲染

Android 之如何優(yōu)化 UI 渲染(上)
Android 之如何優(yōu)化 UI 渲染(下)


在 Android 中,如果想要獲取 View 的某些狀態(tài),大家肯定使用過 ViewTreeObserver,通過名字也可以得知它是 ViewTree 的觀察者,先看下它的自我介紹:

ViewTreeObserver 用于注冊可被全局通知的 Listener 在 ViewTree 狀態(tài)發(fā)生變化時(shí)。這些 Listeners 包括 View 開始繪制、窗口焦點(diǎn)發(fā)生變化等場景。ViewTreeObserver 不能被外部實(shí)例化,只能通過 View 的 getViewTreeObserver() 方式獲取。

  • ViewTree,一般 Activity 包含多個(gè) View 形成 View Hierachy 的樹形結(jié)構(gòu)

也就是 ViewTreeObserver 為應(yīng)用提供了全局監(jiān)聽的 View 狀態(tài)變化,那具體可以幫助我們監(jiān)聽哪些狀態(tài)呢?

序號 Linster 作用
1 OnWindowAttachListener 當(dāng)視圖層次結(jié)構(gòu)關(guān)聯(lián)到窗口或與之分離時(shí)回調(diào)
2 OnWindowFocusChangeListener 當(dāng)視圖層次結(jié)構(gòu)的窗口焦點(diǎn)狀態(tài)發(fā)生變化時(shí)回調(diào)
3 OnGlobalFocusChangeListener 當(dāng)視圖樹中的焦點(diǎn)狀態(tài)更改時(shí)回調(diào)
4 OnGlobalLayoutListener 當(dāng)全局布局狀態(tài)或視圖樹中視圖的可見性更改時(shí)回調(diào)
5 OnPreDrawListener 當(dāng)視圖即將繪制時(shí)回調(diào)
6 OnDrawListener 當(dāng)視圖樹即將繪制時(shí)
7 OnTouchModeChangeListener 當(dāng)觸摸模式改變時(shí)回調(diào)
8 OnScrollChangedListener 當(dāng)視圖樹中的某些內(nèi)容被滾動(dòng)時(shí)回調(diào)
9 ... 被 @hide 聲明

Listeners 在 ViewTreeObserver 中的管理:

private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
private ArrayList<OnDrawListener> mOnDrawListeners;
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;

ViewTreeObserver 內(nèi)部使用 CopyOnWriteArrayList 保存相關(guān) Listener,這主要考慮可能出現(xiàn)的多線程場景,不過熟悉它的朋友知道,之所以叫做 CopyOnWrite 就是當(dāng)添加元素的時(shí)候,不直接向當(dāng)前容器添加,而是先將當(dāng)前容器進(jìn)行Copy,復(fù)制出一個(gè)新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。如果頻發(fā)的申請與釋放其實(shí)對內(nèi)存還是有一定影響的。

ViewTreeObserver 不能被外部實(shí)例化,只能通過 view.getViewTreeObserver() 方式獲取:

 public ViewTreeObserver getViewTreeObserver() {
    if (mAttachInfo != null) {
        // 在繪制流程開始時(shí),才會(huì)被賦值,
        // 此時(shí)所有的View共用ViewRootImpl中提供的ViewTreeObserver
        return mAttachInfo.mTreeObserver;
    }
    if (mFloatingTreeObserver == null) {
        // 懶加載,為當(dāng)前View創(chuàng)建ViewTreeObserver
        mFloatingTreeObserver = new ViewTreeObserver(mContext);
    }
    return mFloatingTreeObserver;
}

注意 mAttachInfo,它的類型是 AttachInfo,內(nèi)部持有一個(gè) ViewTreeObserver,mAttachInfo 在 View 繪制任務(wù)的開始階段被賦值,但是在此之前,每個(gè) View 會(huì)臨時(shí)創(chuàng)建一個(gè) ViewTreeObserver 用于保存當(dāng)前添加的 Listener。View 一旦被關(guān)聯(lián) AttachInfo,它會(huì)合并該 View 的 ViewTreeObserver,后續(xù)的添加的 Listener 都被保存在該 AttachInfo 內(nèi)部的 ViewTreeObserver,即同一個(gè) View Hierarchy 內(nèi)所有 View 共用同一個(gè) ViewTreeObserver。該過程在后面會(huì)分析到。

獲取到 ViewTreeObserver 后就可以添加 Listener 了,ViewTreeObserver 內(nèi)部對所有保存 Listener 的容器均采用懶加載方式管理,這里僅以 OnWindowAttachListener 為例:

public void addOnWindowAttachListener(OnWindowAttachListener listener) {
    // 檢查當(dāng)前ViewTreeObserver是否可用
    // 當(dāng)繪制流程開始時(shí),所有 View 內(nèi)部的ViewTreeObserver 替換為
    // ViewRootImpl 內(nèi)部的,故在同一個(gè) View Hierarchy 內(nèi)共用同一個(gè)。
    checkIsAlive();

    if (mOnWindowAttachListeners == null) {
       // 當(dāng)使用時(shí)在創(chuàng)建
        mOnWindowAttachListeners
                = new CopyOnWriteArrayList<OnWindowAttachListener>();
    }

    mOnWindowAttachListeners.add(listener);
}

具體的 Listener 是在什么時(shí)機(jī)被回調(diào)的呢?既然所有 View(同一個(gè) View Hierarchy 內(nèi)) 的 ViewTreeObserver 最終要被合并到 AttachInfo 內(nèi)的 ViewTreeObserver,在合并之前肯定不會(huì)被調(diào)用,所以,此時(shí)有必要去跟蹤下 AttachInfo 被關(guān)聯(lián)的時(shí)機(jī):

 /**執(zhí)行View繪制任務(wù)*/
private void performTraversals() {

   // ... 省略

   // host是DecorView
   // 每個(gè)ViewRootImpl包含一個(gè)AttachInfo
   // 為所有的childView關(guān)聯(lián)AttachInfo,即同一個(gè)View Hierarchy內(nèi)共用一個(gè)AttachInfo  
   // AttachInfo 中包含ViewTreeObserver
   host.dispatchAttachedToWindow(mAttachInfo, 0);
   // 回調(diào) OnWindowAttachedListener
   mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);

   ...

   // 開始測量
   performMeasure();
   ...

   // 開始布局
   performLayout();

   ...

   // 開始繪制
   performDraw();

    ...
}

在 Activity 中,View 繪制任務(wù)的開始時(shí)機(jī)是在 ActivityThread 的 handleResumeActivity 方法,在該方法首先完成 onResume 生命周期方法回調(diào)。然后發(fā)起 View 繪制任務(wù),最終會(huì)執(zhí)行到 ViewRootImpl 的 performTraversals 方法,在該方法真正開始執(zhí)行 View 的三大繪制流程。具體你可以參考《深入 Activity 三部曲(3)之 View 繪制流程

不過,今天我們要關(guān)注的是 ViewTreeObserver,注意上面的 host,它的實(shí)際類型是 DecorView。具體可以參考這里《View 繪制流程之 setContentView() 到底做了什么 ?

  • 每個(gè) Activity 都有一個(gè)關(guān)聯(lián)的 Window 對象(實(shí)際類型是 PhoneWindow),用來描述應(yīng)用程序窗口,每個(gè)窗口內(nèi)部又包含一個(gè) DecorView 對象(繼承自 FrameLayout),DecorView 用來描述窗口的根視圖 — xml 布局。

這里實(shí)際調(diào)用其父類 ViewGroup 的 dispatchAttachedToWindow 方法,參數(shù) mAttachInfo 很關(guān)鍵,看下它在 ViewRootImpl 中的聲明:

// AttachInfo 內(nèi)部包含一個(gè)ViewTreeObserver
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
            context);

ViewGroup 的 dispatchAttachedToWindow 方法如下:

// ViewGroup重寫View的dispatchAttachedToWindow
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
    super.dispatchAttachedToWindow(info, visibility);
    mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

    final int count = mChildrenCount;
    final View[] children = mChildren;
    // 遍歷所有childView
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        // 調(diào)用子View的dispatchAttachedToWindow
        child.dispatchAttachedToWindow(info,
                combineVisibility(visibility, child.getVisibility()));
    }
}

可以看到遍歷所有 childView,將 ViewRootImpl 的 AttachInfo 作為參數(shù)調(diào)用子 View 的 dispatchAttachedToWindow 方法:

// View的dispatchAttachedToWindow
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    // 給當(dāng)前View賦值A(chǔ)ttachInfo,此時(shí)所有的View共用同一個(gè)AttachInfo(同一個(gè)ViewRootImpl內(nèi))
    mAttachInfo = info;
    // View浮層,是在Android 4.3添加的
    if (mOverlay != null) {
        // 任何一個(gè)View都有一個(gè)ViewOverlay
        // ViewGroup的是ViewGroupOverlay
        // 它區(qū)別于直接在類似RelativeLaout/FrameLayout添加View,通過ViewOverlay添加的元素沒有任何事件
        // 此時(shí)主要分發(fā)給這些View浮層
        mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
    }
    mWindowAttachCount++;
    // We will need to evaluate the drawable state at least once.
    mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
    if (mFloatingTreeObserver != null) {
        // 合并當(dāng)前View的ViewTreeObserver到ViewRootImpl的ViewTreeObserver
        info.mTreeObserver.merge(mFloatingTreeObserver);
        mFloatingTreeObserver = null;
    }

    registerPendingFrameMetricsObservers();

    if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER) != 0) {
        mAttachInfo.mScrollContainers.add(this);
        mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
    }
    // Transfer all pending runnables.
    if (mRunQueue != null) {
        // 執(zhí)行使用View.post的任務(wù),info是AttachInfo
        mRunQueue.executeActions(info.mHandler);
        // 保存延遲任務(wù)的隊(duì)列被置為null,因?yàn)榇藭r(shí)所有的View共用AttachInfo
        mRunQueue = null;
    }
    performCollectViewAttributes(mAttachInfo, visibility);
    // 回調(diào)View的onAttachedToWindow方法
    // 在Activity的onResume方法中調(diào)用,但是在View繪制流程之前
    onAttachedToWindow();

    ListenerInfo li = mListenerInfo;
    final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
            li != null ? li.mOnAttachStateChangeListeners : null;
    if (listeners != null && listeners.size() > 0) {
        for (OnAttachStateChangeListener listener : listeners) {
            // 通知所有監(jiān)聽View已經(jīng)onAttachToWindow的客戶端,即view.addOnAttachStateChangeListener();
            // 但此時(shí)View還沒有開始繪制,不能正確獲取測量大小或View實(shí)際大小
            listener.onViewAttachedToWindow(this);
        }
    }

    int vis = info.mWindowVisibility;
    if (vis != GONE) {
        onWindowVisibilityChanged(vis);
        if (isShown()) {
            // Calling onVisibilityAggregated directly here since the subtree will also
            // receive dispatchAttachedToWindow and this same call
            onVisibilityAggregated(vis == VISIBLE);
        }
    }

    // 回調(diào)View的onVisibilityChanged
    // 注意這時(shí)候View繪制流程還未真正開始
    onVisibilityChanged(this, visibility);

    if ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
        // If nobody has evaluated the drawable state yet, then do it now.
        refreshDrawableState();
    }
    needGlobalAttributesUpdate(false);

    notifyEnterOrExitForAutoFillIfNeeded(true);
}

在該方法,首先將 AttachInfo 賦值給當(dāng)前 childView,記得前面分析到的 addXxxListener(),此后所有添加的 Listener 都被保存在該 AttachInfo 的 ViewTreeObserver 內(nèi)

這里還要著重看下 info.mTreeObserver.merge(),前面有提到,View 在被關(guān)聯(lián) AttachInfo 之前,每個(gè) View 擁有自己臨時(shí)的 ViewTreeObserver,在繪制任務(wù)的開始階段,會(huì)為每個(gè) View 關(guān)聯(lián)一個(gè) AttachInfo,此時(shí)便將當(dāng)前 View 的 ViewTreeObserver 合并到 AttachInfo 的 ViewTreeObserver。 看下這一合并過程 ViewTreeObserver 的 merge 方法:

/**
 * 在 ViewRootImpl 的 dispatchAttachedToWindow 方法被調(diào)用
 * @param observer 子View的ViewTreeObserver
 */
void merge(ViewTreeObserver observer) {
    // 合并OnWindowAttachListener
    if (observer.mOnWindowAttachListeners != null) {
        if (mOnWindowAttachListeners != null) {
            // 添加到AttachInfo的ViewTreeObserver
            mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
        } else {
            // 首次直接賦值
            mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
        }
    }
    // 合并OnWindowFocusListener
    if (observer.mOnWindowFocusListeners != null) {
        if (mOnWindowFocusListeners != null) {
            mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
        } else {
            mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
        }
    }
    // 合并OnGlobalFocusListener
    if (observer.mOnGlobalFocusListeners != null) {
        if (mOnGlobalFocusListeners != null) {
            mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
        } else {
            mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners;
        }
    }

    // ... 省略

}

可以看到,依次將 View 內(nèi)部的 ViewTreeObserver 合并到 AttachInfo 的 ViewTreeObserver。

至此,ViewTreeObserver 的保存和管理機(jī)制就算是搞清楚了,那它們分別在什么時(shí)候被回調(diào)通知呢?


1. OnWindowAttachListener

注意上面貼出的 performTraversals 方法,該方法將依次完成 View 的三大繪制流程,不過在此之前先回調(diào) AttachInfo 內(nèi) ViewTreeObserver 的 dispatchOnWindowAttachChange 方法:

/*
 * 通知已注冊的監(jiān)聽器窗口已被附加/分離。
 */
final void dispatchOnWindowAttachedChange(boolean attached) {
    //注意:由于使用了CopyOnWriteArrayList,必須使用迭代器來
    //執(zhí)行調(diào)度。迭代器是對偵聽器的安全防護(hù)
    //可以通過調(diào)用各種添加/刪除方法來改變列表。這樣可以防止
    //當(dāng)我們迭代數(shù)組時(shí),數(shù)組不會(huì)被修改。
    final CopyOnWriteArrayList<OnWindowAttachListener> listeners
            = mOnWindowAttachListeners;
    if (listeners != null && listeners.size() > 0) {
        for (OnWindowAttachListener listener : listeners) {
            if (attached) {
                // View 被附加到窗口
                listener.onWindowAttached();
            } else {
                // View 從窗口中分離
                listener.onWindowDetached();
            }
        }
    }
}

即 onWindowAttached 方法的調(diào)用在 Activity 生命周期 onResume 方法之后,在繪制流程之前。

  • 此時(shí) View 的繪制流程還未開始,在該方法不能直接獲取到 View 的實(shí)際寬高
 view.getViewTreeObserver().addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
         @Override
         public void onWindowAttached() {
             // 這里不能正確獲取到 View 的寬高
             int width = view.getWidth();
             int height = view.getHeight();
         }

         @Override
         public void onWindowDetached() {
             // onWindowDetached 的回調(diào)時(shí)機(jī)又在什么時(shí)候呢?
         }
  });

那 onWindowDetached 方法是在什么時(shí)候被回調(diào)呢?又在 Activity 的哪個(gè)生命周期階段呢?

它的調(diào)用時(shí)機(jī)是在 ActivityThread 的 handleDestoryActivity 方法,在該方法首先回調(diào) Activity 的 onDestory 生命周期方法,然后將當(dāng)前窗口從 WindowManager 移除,最終調(diào)用到 ViewRootImpl 的 doDie(),在該方法將完成 dispatchDetachedFromWindow 通知。感興趣的朋友可以跟蹤下這一過程或參考這里

void dispatchDetachedFromWindow() {
    if (mView != null && mView.mAttachInfo != null) {
        // 回調(diào) OnWindowAttachListener 的 onWindowDetached 方法
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
        mView.dispatchDetachedFromWindow();
    }
    // ... 省略 
}
  • 即 onWindowAttached() 在 Activity 生命周期 onResume 方法之后,但是在 View 繪制流程之前;onWindowDetached() 在 Activity 生命周期 onDestory 方法之后,此時(shí) View 已經(jīng)從窗口中移除。
2. OnWindowFocusChangeListener

OnWindowFocusChangeListener 表示當(dāng)窗口焦點(diǎn)發(fā)生變化時(shí)回調(diào),看下它在 ViewTreeObserver 內(nèi)的分發(fā)過程:

    /**
     * 通知已注冊的偵聽器窗口焦點(diǎn)已更改。
     */
    final void dispatchOnWindowFocusChange(boolean hasFocus) {
        final CopyOnWriteArrayList<OnWindowFocusChangeListener> listeners
                = mOnWindowFocusListeners;
        if (listeners != null && listeners.size() > 0) {
            for (OnWindowFocusChangeListener listener : listeners) {
                // 回調(diào)OnWindowFocusChangeListener的onWindowFocusChanged方法
                listener.onWindowFocusChanged(hasFocus);
            }
        }
    }

它是在哪里被回調(diào)的呢?猜測肯定還是離不開 ViewRootImpl。查找發(fā)現(xiàn)在 ViewRootImpl 的靜態(tài)內(nèi)部類 W 中,簡單看下這一過程:

/**
 * mWindow是ViewRootImpl的靜態(tài)內(nèi)部類
 * 內(nèi)部持有當(dāng)前ViewRootImpl
 */
static class W extends IWindow.Stub {
    //弱引用持有當(dāng)前ViewRootImpl
    //因?yàn)樗鼘⒈槐4嬖赪indowManagerService
    private final WeakReference<ViewRootImpl> mViewAncestor;
    private final IWindowSession mWindowSession;

    W(ViewRootImpl viewAncestor) {
        mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
        mWindowSession = viewAncestor.mWindowSession;
    }
    
    /**
     * 由遠(yuǎn)程 WMS 通知窗口焦點(diǎn)發(fā)生變化
     */
    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
       final ViewRootImpl viewAncestor = mViewAncestor.get();
       if (viewAncestor != null) {
           // ViewRootImpl的windowFocusChanged方法
           viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
       }
    }

      // ... 省略
}

W 本質(zhì)是一個(gè) Binder 對象,內(nèi)部通過弱引用持有當(dāng)前的 ViewRootImpl,它將作為客戶端保存在 WMS(WindowManagerService),用來和 WindowSession 交互,實(shí)際間接在與 WMS 交互。

windowFocusChanged 方法最終會(huì)發(fā)送消息到當(dāng)前繪制線程,關(guān)鍵代碼如下:

case MSG_WINDOW_FOCUS_CHANGED: {
    if(mView != null){
       // 通知 View 窗口焦點(diǎn)發(fā)生變化
       mView.dispatchWindowFocusChanged(hasWindowFocus);
       // 通知 ViewTreeObserver 窗口焦點(diǎn)發(fā)生變化
       mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
    }
}
3. OnGlobalFocusChangeListener

OnGlobalFocusChangeListener 表示當(dāng)視圖樹中的焦點(diǎn)狀態(tài)更改時(shí)回調(diào),它在 ViewTreeObserver 內(nèi)的分發(fā)機(jī)制如下:

/**
 * 通知已注冊的偵聽器焦點(diǎn)已更改。
 */
final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
    final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
    if (listeners != null && listeners.size() > 0) {
        for (OnGlobalFocusChangeListener listener : listeners) {
            // 回調(diào) onGlobalFocusChanged 方法
            listener.onGlobalFocusChanged(oldFocus, newFocus);
        }
    }
}
Parameters 說明
oldFocus 之前獲取到焦點(diǎn)的視圖
newFocus 當(dāng)前獲取到焦點(diǎn)的視圖
  • 注意 oldFocus 和 newFocus 都有可能為 null。

如果要監(jiān)聽某個(gè) View 是否獲取到焦點(diǎn),我們可以使用如下方式:

mView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
      @Override
      public void onFocusChange(View v, boolean hasFocus) {
          // 監(jiān)聽當(dāng)前 View 是否獲取到焦點(diǎn)
      }
});

不過,當(dāng)要監(jiān)聽的 View 數(shù)量較多時(shí),這種方式好像就不太友好了,此時(shí)我們便可以使用如下方式,監(jiān)聽整個(gè)視圖樹內(nèi) View 焦點(diǎn)的變化:

mSplash.getViewTreeObserver().addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
    @Override
    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
       // 監(jiān)聽整個(gè)視圖樹內(nèi)的View焦點(diǎn)變化
    }
});
4. OnGlobalLayoutListener

OnGlobalLayoutListener 表示視圖樹中的的布局狀態(tài)或視圖的可見性更改時(shí)回調(diào)。在 ViewTreeObserver 內(nèi)的分發(fā)過程如下:

/**
 * 通知已注冊的偵聽器發(fā)生了全局布局。如果您正在強(qiáng)制視圖或視圖層次結(jié)構(gòu)上的布局不附加到窗口或處于消失狀態(tài),則可以手動(dòng)調(diào)用此方法
 */
public final void dispatchOnGlobalLayout() {
    final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
    if (listeners != null && listeners.size() > 0) {
        CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
        try {
            int count = access.size();
            for (int i = 0; i < count; i++) {
                // 回調(diào) onGlobalLayout 方法
                access.get(i).onGlobalLayout();
            }
        } finally {
            listeners.end();
        }
    }
}

注意保存 OnGlobalLayoutListener 的容器是 CopyOnWriteArray,而非 CopyOnWriteArrayList,前者是 ViewTreeObserver 的靜態(tài)內(nèi)部類,后者是 Java 內(nèi)置的并發(fā)工具類,兩者有什么區(qū)別嗎?CopyOnWriteArray 在沒有遍歷任務(wù)時(shí)(未調(diào)用 start()),此時(shí)執(zhí)行 add 操作直接添加到原容器,否則才會(huì)執(zhí)行 CopyOnWrite,這有助于減少內(nèi)存抖動(dòng)場景的發(fā)生

如果僅從名字來看,它應(yīng)該和 View 的 layout 階段有關(guān),下面就再看下 View 的繪制流程:

private void  performTraversals{
    // ... 省略

    // View的測量階段
    performMeasure();
    // View 的布局階段
    performLayout();

    ...
    // 關(guān)鍵方法在這里
    if (triggerGlobalLayoutListener) {
        mAttachInfo.mRecomputeGlobalAttributes = false;
        // 布局階段完成之后回調(diào) OnGlobalLayoutListener,
        // 在該方法中可以正確獲取 View 的實(shí)際寬高
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
     }  

     ...
    
     // View 的繪制階段
     performDraw();

 }

dispatchOnGlobalLayout() 的回調(diào)是在 performLayout 方法之后,此時(shí)視圖樹的布局階段已經(jīng)完成,在該方法我們可以正確獲取到 View 的實(shí)際寬高,具體你可以參考《深入 Activity 三部曲(3)之 View 繪制流程》。

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // 正確獲取到View的寬高
            int width = view.getWidth();
            int height = view.getHeight();
        }
});

注意此時(shí) View 的繪制任務(wù)還未開始!

5. OnPreDrawListener

OnPreDrawListener 表示當(dāng)視圖即將繪制時(shí)回調(diào),它在 ViewTreeObserver 的分發(fā)機(jī)制如下:

public final boolean dispatchOnPreDraw() {
        boolean cancelDraw = false;
        final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
        if (listeners != null && listeners.size() > 0) {
            // 調(diào)用了 start,此時(shí)如果有其他線程要 add 內(nèi)容
            // 就會(huì)執(zhí)行copyOnWrite
            CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
            try {
                int count = access.size();
                for (int i = 0; i < count; i++) {
                    // 回調(diào) onPreDraw 方法
                    cancelDraw |= !(access.get(i).onPreDraw());
                }
            } finally {
                listeners.end();
            }
        }
       return cancelDraw;
 }

OnPreDraw 和 OnDrawListener 兩者有什么區(qū)別呢?Pre 表達(dá)的應(yīng)該是 Previous,即在 Draw 之前,直接看下 View 的繪制任務(wù):

// 調(diào)用 AttachInfo 內(nèi) ViewTreeObserver 的 dispatchOnPreDraw方法
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
// 只有有一個(gè) Listener 返回true,將重新發(fā)起繪制流程
 if (!cancelDraw && !newSurface) {
      if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
           for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();
            }
            mPendingTransitions.clear();
       }

        //執(zhí)行繪制操作
        performDraw();
  }else {
      if (isViewVisible) {
           // 重新發(fā)起繪制流程,measure、layout 
           scheduleTraversals();
       } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
           mPendingTransitions.clear();
       }
   }

如果 dispatchOnPreDraw() 方法返回 true,此時(shí)將攔截 performDraw() 的繪制任務(wù),重新發(fā)起繪制流程 scheduleTraversals()。

其實(shí)它的主要作用是在繪制任務(wù)之前給一次攔截機(jī)會(huì),例如有新的 View 需要添加到當(dāng)前視圖樹中,此時(shí)可以攔截該次繪制任務(wù),重新發(fā)起 measure、layout 流程。

6. OnDrawListener

OnDrawListener 與 OnPreDrawListener 類似,后者可以攔截繪制任務(wù),重新發(fā)起繪制流程,在 ViewTreeObserver 的分發(fā)機(jī)制如下:

/**
   * 通知已注冊的偵聽器繪圖即將開始。
   */
  public final void dispatchOnDraw() {
      if (mOnDrawListeners != null) {
          mInDispatchOnDraw = true;
          final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
          int numListeners = listeners.size();
          for (int i = 0; i < numListeners; ++i) {
              // 通知View開始繪制
              listeners.get(i).onDraw();
          }
          mInDispatchOnDraw = false;
      }
  }

OnDraw,它肯定和 View 的繪制任務(wù)有關(guān)!還是要從 ViewRootImpl 入手,View 的繪制任務(wù) performDraw 方法最終會(huì)調(diào)用到 draw():

private void draw(boolean fullRedrawNeeded) {
    // 窗口都關(guān)聯(lián)有一個(gè) Surface
    // 在 Android 中,所有的元素都在 Surface 這張畫紙上進(jìn)行繪制和渲染,
    // 普通 View(例如非 SurfaceView 或 TextureView) 是沒有 Surface 的,
    // 一般 Activity 包含多個(gè) View 形成 View Hierachy 的樹形結(jié)構(gòu),只有最頂層的 DecorView 才是對 WindowManagerService “可見的”。
    // 而為普通 View 提供 Surface 的正是 ViewRootImpl。
    Surface surface = mSurface;
    if (!surface.isValid()) {
        // Surface 是否還有效
        return;
    }

    // 跟蹤FPS
    if (DEBUG_FPS) {
        trackFPS();
    }

    ...

    scrollToRectOrFocus(null, false);

    if (mAttachInfo.mViewScrollChanged) {
        mAttachInfo.mViewScrollChanged = false;
        // 注意 OnScrollChangedListener 在這里回調(diào)
        mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
    }

    ...

    // 通知View開始繪制
    mAttachInfo.mTreeObserver.dispatchOnDraw();

    ...

    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
            // 是否開啟了硬件加速,
            boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
            ...

            // 開啟硬件加速繪制執(zhí)行這里,最終還是執(zhí)行View的draw開始
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
        } else {               
            ...

            // 最終調(diào)用到drawSoftware
            // surface,每個(gè) View 都由某一個(gè)窗口管理,而每一個(gè)窗口都關(guān)聯(lián)有一個(gè) Surface
            // mDirty.set(0, 0, mWidth, mHeight); dirty 表示畫紙尺寸,對于DecorView,left = 0,
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }

    ...
}

注意如下代碼調(diào)用:

// 回調(diào) ViewTreeObserver內(nèi)的OnDrawListener
mAttachInfo.mTreeObserver.dispatchOnDraw() ;

需要注意,此時(shí) View 繪制流程的前兩個(gè)階段:measure 和 layout 都已經(jīng)完成。通過該 Listener 可以監(jiān)聽開始執(zhí)行繪制任務(wù)。

7. OnTouchModeChangeListener

OnTouchModeChangeListener 表示當(dāng)觸摸模式改變時(shí)回調(diào),它在 ViewTreeObserver 的分發(fā)機(jī)制如下:

/**
 * 通知已注冊的偵聽器觸摸模式已更改。
 */
final void dispatchOnTouchModeChanged(boolean inTouchMode) {
    final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
                mOnTouchModeChangeListeners;
    if (listeners != null && listeners.size() > 0) {
        for (OnTouchModeChangeListener listener : listeners) {
            // 回調(diào) onTouchModeChanged 方法
            listener.onTouchModeChanged(inTouchMode);
        }
    }
}

前面分析 OnWindowFocusChangeListener 的回調(diào)是通過 ViewRootImpl 靜態(tài)內(nèi)部類 W 完成的,在該類中 windowFocusChanged 方法接收遠(yuǎn)程 WMS 的回調(diào)通知,發(fā)送消息到當(dāng)前繪制線程:

case MSG_WINDOW_FOCUS_CHANGED: {

    if(hasWindowFocus){
        boolean inTouchMode = msg.arg2 != 0
        // 這里將回調(diào) OnTouchModeChangeListener
       ensureTouchModeLocally(inTouchMode)
    }
    ... 下面回調(diào) dispatchWindowFocusChanged 方法
}

ensureTouchModeLocally 方法將會(huì)完成該過程:

private boolean ensureTouchModeLocally(boolean inTouchMode) {
    if (mAttachInfo.mInTouchMode == inTouchMode) return false;

    mAttachInfo.mInTouchMode = inTouchMode;
    // 回調(diào)AttachInfo內(nèi)ViewTreeObserver的dispatchOnTouchModeChanged方法
    mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);

    return (inTouchMode) ? enterTouchMode() : leaveTouchMode();
}
8. OnScrollChangedListener 回調(diào)時(shí)機(jī)

OnScrollChangeListener 表示當(dāng)視圖樹中的某些內(nèi)容被滾動(dòng)時(shí)回調(diào),它在 ViewTreeObserver 的分發(fā)機(jī)制如下:

/**
 * 通知已注冊的偵聽器某些內(nèi)容已滾動(dòng)。
 */
final void dispatchOnScrollChanged() {
    final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
    if (listeners != null && listeners.size() > 0) {
        CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
        try {
            int count = access.size();
            for (int i = 0; i < count; i++) {
                // 回調(diào) onScrollChanged
                access.get(i).onScrollChanged();
            }
        } finally {
            listeners.end();
        }
    }
}

是否有注意到前面分析 OnDrawListener 時(shí)的繪制方法 draw,在調(diào)用 ViewTreeObserver 的 dispatchOnDraw 方法之前,會(huì)根據(jù)情況率先執(zhí)行 ViewTreeObserver 的 dispachOnScrollChanged()。

  • OnScrollChangedListener 回調(diào)在 OnDrawListener 之前,用于通知外部當(dāng)前視圖樹中有 View 發(fā)生滾動(dòng)。

以上便是個(gè)人在學(xué)習(xí) ViewTreeObserver 時(shí)的體會(huì)和總結(jié) ,文中如有不妥或有更好的分析結(jié)果,歡迎您的指正。

文章如果對你有幫助,請留個(gè)贊吧!


擴(kuò)展閱讀

其他系列專題

Android 存儲(chǔ)優(yōu)化系列專題
Android 之不要濫用 SharedPreferences
Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失
Android 對象序列化之你不知道的 Serializable
Android 對象序列化之 Parcelable 取代 Serializable ?
Android 對象序列化之追求完美的 Serial

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,973評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,953評論 2 373