View的onAttachedToWindow和onDetachedFromWindow的調用時機分析
緣起
筆者為什么會挑這個話題,是因為長時間以來我自己對這2個方法一直有些疑惑,比如:
- 為啥叫 onAttachedToWindow 而不是 onAttachedToActivity , Window 又是什么,在哪里?畢竟我們平時絕大多數時候接觸到的是 Activity 啊;
- Activity 有明確的生命周期方法,但 View 卻沒有,那么這2個方法可以認為是 View 的嗎?它們又何時會被調用呢?
慢慢地隨著在這一行逐漸深入,閱讀了些系統源碼,開始對這些問題有了自己的答案或者說更加深刻的認識。這篇文章嘗試將筆者的這些理解、認識說清楚,希望能幫助更多人加深認識。
onAttachedToWindow的調用過程
我們在前面Activity啟動過程的文章中說過,在 ActivityThread.handleResumeActivity 的過程中,會將Act的DecorView添加到 WindowManager 中,可能很多人一開始會覺得 WindowManager 是一個具體的類,但是實際上它卻只是個繼承了 ViewManager 的接口,具體代碼如下:
/** Interface to let you add and remove child views to an Activity. To get an instance
of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/
public interface ViewManager
{
/**
- Assign the passed LayoutParams to the passed View and add the view to the window.
- <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
- errors, such as adding a second view to a window without removing the first view.
- <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
- secondary {@link Display} and the specified display can't be found
- (see {@link android.app.Presentation}).
- @param view The view to be added to this window.
@param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}</code></pre>
而 WindowManager 的樣子差不多是這樣,如下圖:

WindowManager源碼
當在 ActivityThread.handleResumeActivity() 方法中調用 WindowManager.addView() 方法時,最終是調去了
WindowManagerImpl.addView() -->
WindowManagerGlobal.addView()
這里我們看下最終調用到的代碼:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
// 這行代碼是本文重點關注的!!!
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}</code></pre>
其中有一句 root.setView(view, wparams, panelParentView); ,正是這行代碼將調用流程轉移到了 ViewRootImpl.setView() 里面,此方法內部最終會觸發 ViewRootImpl.performTraversals() 方法,這個方法就是我們熟悉的View從無到有要經歷的3個階段(measure, layout, draw),不過這個方法內部和我們這里討論的內容相關的是其1364行代碼: host.dispatchAttachedToWindow(mAttachInfo, 0); ,這里的host就是Act的DecorView(FrameLayout的子類),我們可以看到是通過這樣的dispatch方法將這個調用沿著View tree分發了下去,我們分別看下ViewGroup和View中這個方法的實現,如下:
// ViewGroup中的實現:
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;
for (int i = 0; i < count; i++) {
final View child = children[i];
// 遞歸調用每個child的dispatchAttachedToWindow方法
// 典型的深度優先遍歷
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
// View中的實現:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//System.out.println("Attached! " + this);
mAttachInfo = info;
if (mOverlay != null) {
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) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
}
// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
// As all views in the subtree will already receive dispatchAttachedToWindow
// traversing the subtree again here is not desired.
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);
}</code></pre>
從源碼我們可以清晰地看到ViewGroup先是調用自己的 onAttachedToWindow() 方法,再調用其每個child的 onAttachedToWindow() 方法,這樣此方法就在整個view樹中遍布開了,注意到visibility并不會對這個方法產生影響。
onDetachedFromWindow的調用過程
和attched對應的,detached的發生是從act的銷毀開始的,具體的代碼調用流程如下:
ActivityThread.handleDestroyActivity() -->
WindowManager.removeViewImmediate() -->
WindowManagerGlobal.removeViewLocked()方法 —>
ViewRootImpl.die() --> doDie() -->
ViewRootImpl.dispatchDetachedFromWindow()
最終會調用到View層次結構的dispatchDetachedFromWindow方法去,對應的代碼如下:
// ViewGroup的:
@Override
void dispatchDetachedFromWindow() {
// If we still have a touch target, we are still in the process of
// dispatching motion events to a child; we need to get rid of that
// child to avoid dispatching events to it after the window is torn
// down. To make sure we keep the child in a consistent state, we
// first send it an ACTION_CANCEL motion event.
cancelAndClearTouchTargets(null);
// Similarly, set ACTION_EXIT to all hover targets and clear them.
exitHoverTargets();
// In case view is detached while transition is running
mLayoutCalledWhileSuppressed = false;
// Tear down our drag tracking
mDragNotifiedChildren = null;
if (mCurrentDrag != null) {
mCurrentDrag.recycle();
mCurrentDrag = null;
}
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
// 先調用child的方法
children[i].dispatchDetachedFromWindow();
}
clearDisappearingChildren();
final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchDetachedFromWindow();
}
// 最后才是自己的
super.dispatchDetachedFromWindow();
}
// View的:
void dispatchDetachedFromWindow() {
AttachInfo info = mAttachInfo;
if (info != null) {
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(GONE);
}
}
// 調用回調
onDetachedFromWindow();
onDetachedFromWindowInternal();
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
imm.onViewDetachedFromWindow(this);
}
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewDetachedFromWindow(this);
}
}
if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
mAttachInfo.mScrollContainers.remove(this);
mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
}
mAttachInfo = null;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchDetachedFromWindow();
}
}
至此, onDetachedFromWindow() 就在整個view樹上傳播開了。
總結
從上面的分析中我們可以得出下面的結論:
- onAttachedToWindow 方法是在Act resume的時候被調用的,也就是act對應的window被添加的時候,且每個view只會被調用一次,父view的調用在前,不論view的visibility狀態都會被調用,適合做些view特定的初始化操作;
- onDetachedFromWindow 方法是在Act destroy的時候被調用的,也就是act對應的window被刪除的時候,且每個view只會被調用一次,父view的調用在后,也不論view的visibility狀態都會被調用,適合做最后的清理操作;
- 這些結論也正好解釋了方法名里帶有window的原因,有些人可能會想,那為啥不叫 onAttachedToActivity/onDetachedFromActivity ,因為在Android里不止是Activity,這里說的內容同樣適用于 Dialog/Toast , Window 只是個虛的概念,是Android抽象出來的,最終操作的實體還是View,這也說明了前面的 WindowManager 接口為啥是從 ViewManager 接口派生的,因為所有一切的基石歸根結底還是對 View 的操作。
來自:http://www.jianshu.com/p/e7b6fa788ae6