View的onAttachedToWindow和onDetachedFromWindow的调用时机分析
TaylorVette
8年前
<h3>缘起</h3> <p>笔者为什么会挑这个话题,是因为长时间以来我自己对这2个方法一直有些疑惑,比如:</p> <ul> <li>为啥叫 onAttachedToWindow 而不是 onAttachedToActivity , Window 又是什么,在哪里?毕竟我们平时绝大多数时候接触到的是 Activity 啊;</li> <li>Activity 有明确的生命周期方法,但 View 却没有,那么这2个方法可以认为是 View 的吗?它们又何时会被调用呢?</li> </ul> <p>慢慢地随着在这一行逐渐深入,阅读了些系统源码,开始对这些问题有了自己的答案或者说更加深刻的认识。这篇文章尝试将笔者的这些理解、认识说清楚,希望能帮助更多人加深认识。</p> <h3>onAttachedToWindow的调用过程</h3> <p>我们在前面Activity启动过程的文章中说过,在 ActivityThread.handleResumeActivity 的过程中,会将Act的DecorView添加到 WindowManager 中,可能很多人一开始会觉得 WindowManager 是一个具体的类,但是实际上它却只是个继承了 ViewManager 的接口,具体代码如下:</p> <pre> <code class="language-java">/** 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> <p>而 WindowManager 的样子差不多是这样,如下图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/57836709dc0b6171721ed1d0383e5dbd.png"></p> <p>WindowManager源码</p> <p>当在 ActivityThread.handleResumeActivity() 方法中调用 WindowManager.addView() 方法时,最终是调去了</p> <pre> <code class="language-java">WindowManagerImpl.addView() --> WindowManagerGlobal.addView()</code></pre> <p>这里我们看下最终调用到的代码:</p> <pre> <code class="language-java">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> <p>其中有一句 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中这个方法的实现,如下:</p> <pre> <code class="language-java">// 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> <p>从源码我们可以清晰地看到ViewGroup先是调用自己的 onAttachedToWindow() 方法,再调用其每个child的 onAttachedToWindow() 方法,这样此方法就在整个view树中遍布开了,注意到visibility并不会对这个方法产生影响。</p> <h3>onDetachedFromWindow的调用过程</h3> <p>和attched对应的,detached的发生是从act的销毁开始的,具体的代码调用流程如下:</p> <pre> <code class="language-java">ActivityThread.handleDestroyActivity() --> WindowManager.removeViewImmediate() --> WindowManagerGlobal.removeViewLocked()方法 —> ViewRootImpl.die() --> doDie() --> ViewRootImpl.dispatchDetachedFromWindow()</code></pre> <p>最终会调用到View层次结构的dispatchDetachedFromWindow方法去,对应的代码如下:</p> <pre> <code class="language-java">// 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(); } }</code></pre> <p>至此, onDetachedFromWindow() 就在整个view树上传播开了。</p> <h3>总结</h3> <p>从上面的分析中我们可以得出下面的结论:</p> <ol> <li>onAttachedToWindow 方法是在Act resume的时候被调用的,也就是act对应的window被添加的时候,且每个view只会被调用一次,父view的调用在前,不论view的visibility状态都会被调用,适合做些view特定的初始化操作;</li> <li>onDetachedFromWindow 方法是在Act destroy的时候被调用的,也就是act对应的window被删除的时候,且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作;</li> <li>这些结论也正好解释了方法名里带有window的原因,有些人可能会想,那为啥不叫 onAttachedToActivity/onDetachedFromActivity ,因为在Android里不止是Activity,这里说的内容同样适用于 Dialog/Toast , Window 只是个虚的概念,是Android抽象出来的,最终操作的实体还是View,这也说明了前面的 WindowManager 接口为啥是从 ViewManager 接口派生的,因为所有一切的基石归根结底还是对 View 的操作。</li> </ol> <p> </p> <p>来自:http://www.jianshu.com/p/e7b6fa788ae6</p> <p> </p>