View的onAttachedToWindow和onDetach

作者: tmp_zhao | 来源:发表于2016-04-16 15:42 被阅读11364次

    缘起

    笔者为什么会挑这个话题,是因为长时间以来我自己对这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的操作。

    相关文章

      网友评论

      • 君莫醉:RecyclerView 列表的view也会调用 很多次
      • passerbywhu:如果是Fragment的话,onAttachedToWindow和onDetachedFromWindow可能会被调用多次的。
        passerbywhu:调用多次应该是这个Fragment被销毁了。destroyView了。如果没有destroyView的话应该是不会被调用的吧。
        tmp_zhao:你说的调多次,实质上是在不同的View实例上调的多次;严格意义上来说,在某个View上还是调了一次。
      • 8e47b82fb2be:博主好像漏掉了fragment下的view的attach/detach喔,期待有机会能拜读博主关于这方面的精彩解析🌹🌹🌹
        8e47b82fb2be:@tmp_zhao 好的,谢谢!
        tmp_zhao:@小二哥的幸福生活 fragment的case里面最终会分别触发ViewGroup的addViewInner 和removeViewInternal这2个方法,它们内部又会调到dispatchAttachedToWindow/dispatchDetachedFromWindow,你可以顺着这个链自己看下源码。

      本文标题:View的onAttachedToWindow和onDetach

      本文链接:https://www.haomeiwen.com/subject/aagylttx.html