UI 優(yōu)化系列專題惋砂,來聊一聊 Android 渲染相關(guān)知識鞠绰,主要涉及 UI 渲染背景知識、如何優(yōu)化 UI 渲染兩部分內(nèi)容祠挫。
UI 優(yōu)化系列專題
- UI 渲染背景知識
《View 繪制流程之 setContentView() 到底做了什么那槽?》
《View 繪制流程之 DecorView 添加至窗口的過程》
《深入 Activity 三部曲(3)View 繪制流程》
《Android 之 LayoutInflater 全面解析》
《關(guān)于渲染,你需要了解什么茸歧?》
《Android 之 Choreographer 詳細分析》
- 如何優(yōu)化 UI 渲染
《Android 之如何優(yōu)化 UI 渲染(上)》
《Android 之如何優(yōu)化 UI 渲染(下)》
在 Android 中倦炒,如果想要獲取 View 的某些狀態(tài),大家肯定使用過 ViewTreeObserver软瞎,通過名字也可以得知它是 ViewTree 的觀察者逢唤,先看下它的自我介紹:
ViewTreeObserver 用于注冊可被全局通知的 Listener 在 ViewTree 狀態(tài)發(fā)生變化時。這些 Listeners 包括 View 開始繪制涤浇、窗口焦點發(fā)生變化等場景鳖藕。ViewTreeObserver 不能被外部實例化,只能通過 View 的 getViewTreeObserver() 方式獲取只锭。
- ViewTree著恩,一般 Activity 包含多個 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)到窗口或與之分離時回調(diào) |
2 | OnWindowFocusChangeListener | 當(dāng)視圖層次結(jié)構(gòu)的窗口焦點狀態(tài)發(fā)生變化時回調(diào) |
3 | OnGlobalFocusChangeListener | 當(dāng)視圖樹中的焦點狀態(tài)更改時回調(diào) |
4 | OnGlobalLayoutListener | 當(dāng)全局布局狀態(tài)或視圖樹中視圖的可見性更改時回調(diào) |
5 | OnPreDrawListener | 當(dāng)視圖即將繪制時回調(diào) |
6 | OnDrawListener | 當(dāng)視圖樹即將繪制時 |
7 | OnTouchModeChangeListener | 當(dāng)觸摸模式改變時回調(diào) |
8 | OnScrollChangedListener | 當(dāng)視圖樹中的某些內(nèi)容被滾動時回調(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)添加元素的時候伍茄,不直接向當(dāng)前容器添加,而是先將當(dāng)前容器進行Copy施逾,復(fù)制出一個新的容器敷矫,然后新的容器里添加元素,添加完元素之后汉额,再將原容器的引用指向新的容器曹仗。如果頻發(fā)的申請與釋放其實對內(nèi)存還是有一定影響的。
ViewTreeObserver 不能被外部實例化蠕搜,只能通過 view.getViewTreeObserver() 方式獲仍趺!:
public ViewTreeObserver getViewTreeObserver() {
if (mAttachInfo != null) {
// 在繪制流程開始時,才會被賦值妓灌,
// 此時所有的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)部持有一個 ViewTreeObserver俱萍,mAttachInfo 在 View 繪制任務(wù)的開始階段被賦值端壳,但是在此之前,每個 View 會臨時創(chuàng)建一個 ViewTreeObserver 用于保存當(dāng)前添加的 Listener枪蘑。View 一旦被關(guān)聯(lián) AttachInfo损谦,它會合并該 View 的 ViewTreeObserver,后續(xù)的添加的 Listener 都被保存在該 AttachInfo 內(nèi)部的 ViewTreeObserver岳颇,即同一個 View Hierarchy 內(nèi)所有 View 共用同一個 ViewTreeObserver照捡。該過程在后面會分析到。
獲取到 ViewTreeObserver 后就可以添加 Listener 了话侧,ViewTreeObserver 內(nèi)部對所有保存 Listener 的容器均采用懶加載方式管理栗精,這里僅以 OnWindowAttachListener 為例:
public void addOnWindowAttachListener(OnWindowAttachListener listener) {
// 檢查當(dāng)前ViewTreeObserver是否可用
// 當(dāng)繪制流程開始時,所有 View 內(nèi)部的ViewTreeObserver 替換為
// ViewRootImpl 內(nèi)部的瞻鹏,故在同一個 View Hierarchy 內(nèi)共用同一個悲立。
checkIsAlive();
if (mOnWindowAttachListeners == null) {
// 當(dāng)使用時在創(chuàng)建
mOnWindowAttachListeners
= new CopyOnWriteArrayList<OnWindowAttachListener>();
}
mOnWindowAttachListeners.add(listener);
}
具體的 Listener 是在什么時機被回調(diào)的呢?既然所有 View(同一個 View Hierarchy 內(nèi)) 的 ViewTreeObserver 最終要被合并到 AttachInfo 內(nèi)的 ViewTreeObserver新博,在合并之前肯定不會被調(diào)用薪夕,所以,此時有必要去跟蹤下 AttachInfo 被關(guān)聯(lián)的時機:
/**執(zhí)行View繪制任務(wù)*/
private void performTraversals() {
// ... 省略
// host是DecorView
// 每個ViewRootImpl包含一個AttachInfo
// 為所有的childView關(guān)聯(lián)AttachInfo赫悄,即同一個View Hierarchy內(nèi)共用一個AttachInfo
// AttachInfo 中包含ViewTreeObserver
host.dispatchAttachedToWindow(mAttachInfo, 0);
// 回調(diào) OnWindowAttachedListener
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
...
// 開始測量
performMeasure();
...
// 開始布局
performLayout();
...
// 開始繪制
performDraw();
...
}
在 Activity 中原献,View 繪制任務(wù)的開始時機是在 ActivityThread 的 handleResumeActivity 方法,在該方法首先完成 onResume 生命周期方法回調(diào)埂淮。然后發(fā)起 View 繪制任務(wù)姑隅,最終會執(zhí)行到 ViewRootImpl 的 performTraversals 方法,在該方法真正開始執(zhí)行 View 的三大繪制流程倔撞。具體你可以參考《深入 Activity 三部曲(3)之 View 繪制流程》
不過粤策,今天我們要關(guān)注的是 ViewTreeObserver,注意上面的 host误窖,它的實際類型是 DecorView。具體可以參考這里《View 繪制流程之 setContentView() 到底做了什么 秩贰?》
- 每個 Activity 都有一個關(guān)聯(lián)的 Window 對象(實際類型是 PhoneWindow)霹俺,用來描述應(yīng)用程序窗口,每個窗口內(nèi)部又包含一個 DecorView 對象(繼承自 FrameLayout)毒费,DecorView 用來描述窗口的根視圖 — xml 布局丙唧。
這里實際調(diào)用其父類 ViewGroup 的 dispatchAttachedToWindow 方法,參數(shù) mAttachInfo 很關(guān)鍵觅玻,看下它在 ViewRootImpl 中的聲明:
// AttachInfo 內(nèi)部包含一個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培漏,此時所有的View共用同一個AttachInfo(同一個ViewRootImpl內(nèi))
mAttachInfo = info;
// View浮層,是在Android 4.3添加的
if (mOverlay != null) {
// 任何一個View都有一個ViewOverlay
// ViewGroup的是ViewGroupOverlay
// 它區(qū)別于直接在類似RelativeLaout/FrameLayout添加View胡本,通過ViewOverlay添加的元素沒有任何事件
// 此時主要分發(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ù)的隊列被置為null,因為此時所有的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();
// 但此時View還沒有開始繪制,不能正確獲取測量大小或View實際大小
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
// 注意這時候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 之前拇泛,每個 View 擁有自己臨時的 ViewTreeObserver滨巴,在繪制任務(wù)的開始階段,會為每個 View 關(guān)聯(lián)一個 AttachInfo碰镜,此時便將當(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 的保存和管理機制就算是搞清楚了,那它們分別在什么時候被回調(diào)通知呢柠横?
1. OnWindowAttachListener
注意上面貼出的 performTraversals 方法窃款,該方法將依次完成 View 的三大繪制流程,不過在此之前先回調(diào) AttachInfo 內(nèi) ViewTreeObserver 的 dispatchOnWindowAttachChange 方法:
/*
* 通知已注冊的監(jiān)聽器窗口已被附加/分離牍氛。
*/
final void dispatchOnWindowAttachedChange(boolean attached) {
//注意:由于使用了CopyOnWriteArrayList晨继,必須使用迭代器來
//執(zhí)行調(diào)度。迭代器是對偵聽器的安全防護
//可以通過調(diào)用各種添加/刪除方法來改變列表搬俊。這樣可以防止
//當(dāng)我們迭代數(shù)組時紊扬,數(shù)組不會被修改。
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 方法之后唉擂,在繪制流程之前餐屎。
- 此時 View 的繪制流程還未開始,在該方法不能直接獲取到 View 的實際寬高
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)時機又在什么時候呢玩祟?
}
});
那 onWindowDetached 方法是在什么時候被回調(diào)呢腹缩?又在 Activity 的哪個生命周期階段呢?
它的調(diào)用時機是在 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 方法之后勒魔,此時 View 已經(jīng)從窗口中移除。
2. OnWindowFocusChangeListener
OnWindowFocusChangeListener 表示當(dāng)窗口焦點發(fā)生變化時回調(diào)菇曲,看下它在 ViewTreeObserver 內(nèi)的分發(fā)過程:
/**
* 通知已注冊的偵聽器窗口焦點已更改冠绢。
*/
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
//因為它將被保存在WindowManagerService
private final WeakReference<ViewRootImpl> mViewAncestor;
private final IWindowSession mWindowSession;
W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
mWindowSession = viewAncestor.mWindowSession;
}
/**
* 由遠程 WMS 通知窗口焦點發(fā)生變化
*/
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
// ViewRootImpl的windowFocusChanged方法
viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
}
}
// ... 省略
}
W 本質(zhì)是一個 Binder 對象,內(nèi)部通過弱引用持有當(dāng)前的 ViewRootImpl喊式,它將作為客戶端保存在 WMS(WindowManagerService)孵户,用來和 WindowSession 交互,實際間接在與 WMS 交互岔留。
windowFocusChanged 方法最終會發(fā)送消息到當(dāng)前繪制線程夏哭,關(guān)鍵代碼如下:
case MSG_WINDOW_FOCUS_CHANGED: {
if(mView != null){
// 通知 View 窗口焦點發(fā)生變化
mView.dispatchWindowFocusChanged(hasWindowFocus);
// 通知 ViewTreeObserver 窗口焦點發(fā)生變化
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
}
}
3. OnGlobalFocusChangeListener
OnGlobalFocusChangeListener 表示當(dāng)視圖樹中的焦點狀態(tài)更改時回調(diào),它在 ViewTreeObserver 內(nèi)的分發(fā)機制如下:
/**
* 通知已注冊的偵聽器焦點已更改献联。
*/
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 | 之前獲取到焦點的視圖 |
newFocus | 當(dāng)前獲取到焦點的視圖 |
- 注意 oldFocus 和 newFocus 都有可能為 null竖配。
如果要監(jiān)聽某個 View 是否獲取到焦點,我們可以使用如下方式:
mView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
// 監(jiān)聽當(dāng)前 View 是否獲取到焦點
}
});
不過里逆,當(dāng)要監(jiān)聽的 View 數(shù)量較多時进胯,這種方式好像就不太友好了,此時我們便可以使用如下方式原押,監(jiān)聽整個視圖樹內(nèi) View 焦點的變化:
mSplash.getViewTreeObserver().addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
@Override
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
// 監(jiān)聽整個視圖樹內(nèi)的View焦點變化
}
});
4. OnGlobalLayoutListener
OnGlobalLayoutListener 表示視圖樹中的的布局狀態(tài)或視圖的可見性更改時回調(diào)胁镐。在 ViewTreeObserver 內(nèi)的分發(fā)過程如下:
/**
* 通知已注冊的偵聽器發(fā)生了全局布局。如果您正在強制視圖或視圖層次結(jié)構(gòu)上的布局不附加到窗口或處于消失狀態(tài)诸衔,則可以手動調(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ù)時(未調(diào)用 start())磁餐,此時執(zhí)行 add 操作直接添加到原容器,否則才會執(zhí)行 CopyOnWrite,這有助于減少內(nèi)存抖動場景的發(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 的實際寬高
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
...
// View 的繪制階段
performDraw();
}
dispatchOnGlobalLayout() 的回調(diào)是在 performLayout 方法之后伴箩,此時視圖樹的布局階段已經(jīng)完成,在該方法我們可以正確獲取到 View 的實際寬高鄙漏,具體你可以參考《深入 Activity 三部曲(3)之 View 繪制流程》嗤谚。
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 正確獲取到View的寬高
int width = view.getWidth();
int height = view.getHeight();
}
});
注意此時 View 的繪制任務(wù)還未開始!
5. OnPreDrawListener
OnPreDrawListener 表示當(dāng)視圖即將繪制時回調(diào)怔蚌,它在 ViewTreeObserver 的分發(fā)機制如下:
public final boolean dispatchOnPreDraw() {
boolean cancelDraw = false;
final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
if (listeners != null && listeners.size() > 0) {
// 調(diào)用了 start巩步,此時如果有其他線程要 add 內(nèi)容
// 就會執(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 表達的應(yīng)該是 Previous桦踊,即在 Draw 之前椅野,直接看下 View 的繪制任務(wù):
// 調(diào)用 AttachInfo 內(nèi) ViewTreeObserver 的 dispatchOnPreDraw方法
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
// 只有有一個 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,此時將攔截 performDraw() 的繪制任務(wù)杖狼,重新發(fā)起繪制流程 scheduleTraversals()炼蛤。
其實它的主要作用是在繪制任務(wù)之前給一次攔截機會,例如有新的 View 需要添加到當(dāng)前視圖樹中蝶涩,此時可以攔截該次繪制任務(wù)理朋,重新發(fā)起 measure、layout 流程子寓。
6. OnDrawListener
OnDrawListener 與 OnPreDrawListener 類似暗挑,后者可以攔截繪制任務(wù),重新發(fā)起繪制流程斜友,在 ViewTreeObserver 的分發(fā)機制如下:
/**
* 通知已注冊的偵聽器繪圖即將開始炸裆。
*/
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 方法最終會調(diào)用到 draw():
private void draw(boolean fullRedrawNeeded) {
// 窗口都關(guān)聯(lián)有一個 Surface
// 在 Android 中,所有的元素都在 Surface 這張畫紙上進行繪制和渲染洛史,
// 普通 View(例如非 SurfaceView 或 TextureView) 是沒有 Surface 的惯殊,
// 一般 Activity 包含多個 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己儒,每個 View 都由某一個窗口管理崎岂,而每一個窗口都關(guān)聯(lián)有一個 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() 冲甘;
需要注意,此時 View 繪制流程的前兩個階段:measure 和 layout 都已經(jīng)完成途样。通過該 Listener 可以監(jiān)聽開始執(zhí)行繪制任務(wù)江醇。
7. OnTouchModeChangeListener
OnTouchModeChangeListener 表示當(dāng)觸摸模式改變時回調(diào),它在 ViewTreeObserver 的分發(fā)機制如下:
/**
* 通知已注冊的偵聽器觸摸模式已更改何暇。
*/
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 方法接收遠程 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 方法將會完成該過程:
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)時機
OnScrollChangeListener 表示當(dāng)視圖樹中的某些內(nèi)容被滾動時回調(diào)赖晶,它在 ViewTreeObserver 的分發(fā)機制如下:
/**
* 通知已注冊的偵聽器某些內(nèi)容已滾動律适。
*/
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 時的繪制方法 draw,在調(diào)用 ViewTreeObserver 的 dispatchOnDraw 方法之前遏插,會根據(jù)情況率先執(zhí)行 ViewTreeObserver 的 dispachOnScrollChanged()捂贿。
- OnScrollChangedListener 回調(diào)在 OnDrawListener 之前,用于通知外部當(dāng)前視圖樹中有 View 發(fā)生滾動胳嘲。
以上便是個人在學(xué)習(xí) ViewTreeObserver 時的體會和總結(jié) 厂僧,文中如有不妥或有更好的分析結(jié)果,歡迎您的指正了牛。
文章如果對你有幫助颜屠,請留個贊吧!
擴展閱讀
- Android 之你真的了解 View.post() 原理嗎鹰祸?
- Android 之 Choreographer 詳細分析
- RenderThread:實現(xiàn)動畫的異步渲染
- Android 之 LayoutInflater 全面解析
其他系列專題
Android 存儲優(yōu)化系列專題
Android 之不要濫用 SharedPreferences
Android 之不要濫用 SharedPreferences(2)— 數(shù)據(jù)丟失
Android 對象序列化之你不知道的 Serializable
Android 對象序列化之 Parcelable 取代 Serializable ?
Android 對象序列化之追求完美的 Serial