onAttachedToWindow和onDetachedFromWindow的调用时机分析
缘起
笔者为什么会挑这个话题,是因为长时间以来我自己对这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);
}
而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 thingstry {// 这行代码是本文重点关注的!!!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的:
@Overridevoid 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 runningmLayoutCalledWhileSuppressed = false;// Tear down our drag trackingmDragNotifiedChildren = 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树上传播开了。
总结
从上面的分析中我们可以得出下面的结论:
onAttachedToWindow方法是在Act resume的时候被调用的,也就是act对应的window被添加的时候,且每个view只会被调用一次,父view的调用在前,不论view的visibility状态都会被调用,适合做些view特定的初始化操作;
onDetachedFromWindow方法是在Act destroy的时候被调用的,也就是act对应的window被删除的时候,且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作;
这些结论也正好解释了方法名里带有window的原因,有些人可能会想,那为啥不叫onAttachedToActivity/onDetachedFromActivity,因为在Android里不止是Activity,这里说的内容同样适用于Dialog/Toast,Window只是个虚的概念,是Android抽象出来的,最终操作的实体还是View,这也说明了前面的WindowManager接口为啥是从ViewManager接口派生的,因为所有一切的基石归根结底还是对View的操作。
转载自:https://www.jianshu.com/p/e7b6fa788ae6
onAttachedToWindow和onDetachedFromWindow的调用时机分析相关推荐
- View的onAttachedToWindow, onDetachedFromWindow的调用时机,使用场景是什么?
原始网页直通车 调用时机 Attached 附加的意思,当 View 附加到 Window 的时候,就会回调 onAttachedToWindow . Detached 分离,拆卸的意思,与 Atta ...
- Transition 调用方法分析
Transition 调用方法分析 TransitionManager.transitionTo(Scene) /*** Change to the given scene, using the* a ...
- Redis 源码解读之 Rehash 的调用时机
Redis 源码解读之 Rehash 的调用时机 背景和问题 本文想要解决的问题 什么时机触发 Rehash 操作? 什么时机实际执行 Rehash 函数? 结论 什么时机触发 Rehash 操作? ...
- Android ViewGroup的draw和onDraw的调用时机
Android ViewGroup的draw和onDraw的调用时机 View.draw和View.onDraw的调用关系 首先,View.draw和View.onDraw是两个不同的方法,只有Vie ...
- 使用PowerShell调用MTools分析MongoDB性能并发送邮件
使用PowerShell调用MTools分析MongoDB性能并发送邮件 问题描述: 在MongoDB日常运维中,经常需要查看连接数的趋势图.慢查询.Overflow语句.连接来源. 解决方案: 1. ...
- EJB调用原理分析 (飞茂EJB)
EJB调用原理分析 EJB调用原理分析 作者:robbin (MSN:robbin_fan AT hotmail DOT com) 版权声明:本文严禁转载,如有转载请求,请和作者联系 一个远程对象至少 ...
- 深入掌握Java技术 EJB调用原理分析
深入掌握Java技术 EJB调用原理分析 一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在 ...
- 关于Activity onNewIntent方法的调用时机
在官方API上的说明如下: http://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.c ...
- (0083)iOS开发之layoutSubviews 的调用时机
在写程序时候遇见layoutSubviews触发时候引起的问题. 思考 1:layoutSubviews 的调用时机? 2:layoutSubviews的用途? layoutSubviews在以下情况 ...
最新文章
- 控件包含代码块,因此无法修改控件集合
- java memcached 存储对象_memcached—向memcached中保存Java实体需注意的问题
- Spring配置数据源(连接池)
- js(Dom+Bom)第二天(1)
- Please, commit your changes or stash them before you can merge.
- 庆功会(信息学奥数一本通-T1269)
- 五一四天假公布后携程机票大涨价 官方如此回应
- python编程可以自学么-风变编程的Python这么火,零基础可以自学吗?
- ArcGIS 设置暂时固定存储地址
- 史上最全高级Java教程总结版(强烈建议收藏)
- Android基于在线地图的轨迹跟踪服务
- 通讯录管理系统(C++基础 汇总案例)
- chrome driver 环境问题
- 导出excel file-saver XLSX
- 怎么判断两个多项式互素_多项式互素的等价条件
- win7总是显示加载计算机,win7系统打开“此电脑”很慢总是在加载不显示的具体办法...
- Excel方向键无法移动单元格/scrollLock解锁方法
- 微信公共平台 “token验证失败”的一个原因
- 手机设备唤醒计算机,手机遥控电脑开机神器!局域网唤醒App
- 谷歌浏览器怎么拦截网页广告 5步解决广告困扰