zoukankan      html  css  js  c++  java
  • 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
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    Atitit.随时间变色特效 ---包厢管理系统的规划
    Atitit.request http乱码的设计防止 检测与解决最近实践p825 attilax总结.doc
    Atitit.request http乱码的设计防止 检测与解决最近实践p825 attilax总结.doc
    atitit.薄伽梵歌overview  attilax 读后感
    Atitit。 《吠陀》 《梨俱吠陀》overview 经读后感  是印度上古时期一些文献的总称
    Atitit。 《吠陀》 《梨俱吠陀》overview 经读后感  是印度上古时期一些文献的总称
    atitit.薄伽梵歌overview  attilax 读后感
    Atitit 《摩奴法典》overivew 读后感 不是由国王 颁布的,而是 僧侣编制
    Atitit 《摩奴法典》overivew 读后感 不是由国王 颁布的,而是 僧侣编制
    Atitit.执行cli cmd的原理与调试
  • 原文地址:https://www.cnblogs.com/chenxibobo/p/14102179.html
Copyright © 2011-2022 走看看