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>