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
88public 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
5ActivityThread.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();
}
}