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

TaylorVette 7年前發布 | 9K 次閱讀 安卓開發 Android開發 移動開發

緣起

筆者為什么會挑這個話題,是因為長時間以來我自己對這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樹上傳播開了。

總結

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

  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/Toast , Window 只是個虛的概念,是Android抽象出來的,最終操作的實體還是View,這也說明了前面的 WindowManager 接口為啥是從 ViewManager 接口派生的,因為所有一切的基石歸根結底還是對 View 的操作。

 

來自:http://www.jianshu.com/p/e7b6fa788ae6

 

 本文由用戶 TaylorVette 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!