@View的onAttachedToWindow和onDetachedFromWindow的調用時機分析


緣起

筆者為什么會挑這個話題,是因為長時間以來我自己對這2個方法一直有些疑惑,比如:

  • 為啥叫onAttachedToWindow而不是onAttachedToActivityWindow又是什么,在哪里?畢竟我們平時絕大多數時候接觸到的是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); } 

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; } } 

其中有一句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); } 

從源碼我們可以清晰地看到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樹上傳播開了。

總結

從上面的分析中我們可以得出下面的結論:

  1. onAttachedToWindow方法是在Act resume的時候被調用的,也就是act對應的window被添加的時候,且每個view只會被調用一次,父view的調用在前,不論view的visibility狀態都會被調用,適合做些view特定的初始化操作;
  2. onDetachedFromWindow方法是在Act destroy的時候被調用的,也就是act對應的window被刪除的時候,且每個view只會被調用一次,父view的調用在后,也不論view的visibility狀態都會被調用,適合做最后的清理操作;
  3. 這些結論也正好解釋了方法名里帶有window的原因,有些人可能會想,那為啥不叫onAttachedToActivity/onDetachedFromActivity,因為在Android里不止是Activity,這里說的內容同樣適用於Dialog/ToastWindow只是個虛的概念,是Android抽象出來的,最終操作的實體還是View,這也說明了前面的WindowManager接口為啥是從ViewManager接口派生的,因為所有一切的基石歸根結底還是對View的操作。


作者:tmp_zhao
鏈接:https://www.jianshu.com/p/e7b6fa788ae6
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM