Android onAttachedToWindow 和 onDetachedFromWindow

01.这两个方法的作用分析

1.1 方法说明

  • 首先View没有明显的生命周期,onDetachedFromWindow()与onAttachedToWindow()方法相对应。
    • ViewGroup先是调用自己的onAttachedToWindow()方法,再调用其每个child的onAttachedToWindow()方法,这样此方法就在整个view树中遍布开了,而visibility并不会对这个方法产生影响。
    • onAttachedToWindow方法是在Activity resume的时候被调用的,也就是act对应的window被添加的时候,且每个view只会被调用一次,父view的调用在前,不论view的visibility状态都会被调用,适合做些view特定的初始化操作;
    • onDetachedFromWindow方法是在Activity destroy的时候被调用的,也就是act对应的window被删除的时候,且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作;
    • 这些结论也正好解释了方法名里带有window的原因,有些人可能会想,那为啥不叫onAttachedToActivity/onDetachedFromActivity,因为在Android里不止是Activity,这里说的内容同样适用于Dialog/Toast,Window只是个虚的概念,是Android抽象出来的,最终操作的实体还是View,这也说明了前面的WindowManager接口为啥是从ViewManager接口派生的,因为所有一切的基石归根结底还是对View的操作。

1.2 onAttachedToWindow()可以做什么?

  • 比如如果不想在构造函数里面addView()添加控件,可以在onAttachedToWindow()方法添加!

    • 这个操作比较合适的时机就是在onDraw()之前——onAttachedToWindow()方法中。
    • 此时View被添加到了窗体上,View有了一个用于显示的Surface,将开始绘制。因此其保证了在onDraw()之前调用,但可能在调用 onDraw(Canvas) 之前的任何时刻,包括调用 onMeasure(int, int) 之前或之后。
    • 比较适合去执行一些初始化操作。(此外在屏蔽Home键的时候也会回调这个方法)

1.3 onDetachedFromWindow可以做什么?

  • 比如我开发的视频播放器封装库

    • 需要开发者使用时,在activity页面销毁时,销毁视频资源。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override
    protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    VideoLogUtil.d("onDetachedFromWindow");
    //onDetachedFromWindow方法是在Activity destroy的时候被调用的,也就是act对应的window被删除的时候,
    //且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作
    //防止开发者没有在onDestroy中没有做销毁视频的优化
    release();
    }

02.onAttachedToWindow的调用过程

  • 在ActivityThread.handleResumeActivity的过程中,会将Activity的DecorView添加到WindowManager中,可能很多人一开始会觉得WindowManager是一个具体的类,但是实际上它却只是个继承了ViewManager的接口。

  • 当在ActivityThread.handleResumeActivity()方法中调用WindowManager.addView()方法,大概走的流程是到WindowManagerImpl.addView() –>WindowManagerGlobal.addView()

  • 然后看看WindowManagerGlobal的addView方法代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    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.
    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中这个方法的实现,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    // 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并不会对这个方法产生影响。

03.onDetachedFromWindow的调用过程

  • 和attched对应的,detached的发生是从act的销毁开始的,具体的代码调用流程如下:

    1
    2
    3
    4
    5
    ActivityThread.handleDestroyActivity() -->
    WindowManager.removeViewImmediate() -->
    WindowManagerGlobal.removeViewLocked()方法 —>
    ViewRootImpl.die() --> doDie() -->
    ViewRootImpl.dispatchDetachedFromWindow()
  • 最终会调用到View层次结构的dispatchDetachedFromWindow方法去,对应的代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    // 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();
    }
    }