缘起

笔者为什么会挑这个话题,是因为长时间以来我自己对这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的调用时机分析相关推荐

  1. View的onAttachedToWindow, onDetachedFromWindow的调用时机,使用场景是什么?

    原始网页直通车 调用时机 Attached 附加的意思,当 View 附加到 Window 的时候,就会回调 onAttachedToWindow . Detached 分离,拆卸的意思,与 Atta ...

  2. Transition 调用方法分析

    Transition 调用方法分析 TransitionManager.transitionTo(Scene) /*** Change to the given scene, using the* a ...

  3. Redis 源码解读之 Rehash 的调用时机

    Redis 源码解读之 Rehash 的调用时机 背景和问题 本文想要解决的问题 什么时机触发 Rehash 操作? 什么时机实际执行 Rehash 函数? 结论 什么时机触发 Rehash 操作? ...

  4. Android ViewGroup的draw和onDraw的调用时机

    Android ViewGroup的draw和onDraw的调用时机 View.draw和View.onDraw的调用关系 首先,View.draw和View.onDraw是两个不同的方法,只有Vie ...

  5. 使用PowerShell调用MTools分析MongoDB性能并发送邮件

    使用PowerShell调用MTools分析MongoDB性能并发送邮件 问题描述: 在MongoDB日常运维中,经常需要查看连接数的趋势图.慢查询.Overflow语句.连接来源. 解决方案: 1. ...

  6. EJB调用原理分析 (飞茂EJB)

    EJB调用原理分析 EJB调用原理分析 作者:robbin (MSN:robbin_fan AT hotmail DOT com) 版权声明:本文严禁转载,如有转载请求,请和作者联系 一个远程对象至少 ...

  7. 深入掌握Java技术 EJB调用原理分析

      深入掌握Java技术 EJB调用原理分析     一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在 ...

  8. 关于Activity onNewIntent方法的调用时机

    在官方API上的说明如下: http://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.c ...

  9. (0083)iOS开发之layoutSubviews 的调用时机

    在写程序时候遇见layoutSubviews触发时候引起的问题. 思考 1:layoutSubviews 的调用时机? 2:layoutSubviews的用途? layoutSubviews在以下情况 ...

最新文章

  1. 控件包含代码块,因此无法修改控件集合
  2. java memcached 存储对象_memcached—向memcached中保存Java实体需注意的问题
  3. Spring配置数据源(连接池)
  4. js(Dom+Bom)第二天(1)
  5. Please, commit your changes or stash them before you can merge.
  6. 庆功会(信息学奥数一本通-T1269)
  7. 五一四天假公布后携程机票大涨价 官方如此回应
  8. python编程可以自学么-风变编程的Python这么火,零基础可以自学吗?
  9. ArcGIS 设置暂时固定存储地址
  10. 史上最全高级Java教程总结版(强烈建议收藏)
  11. Android基于在线地图的轨迹跟踪服务
  12. 通讯录管理系统(C++基础 汇总案例)
  13. chrome driver 环境问题
  14. 导出excel file-saver XLSX
  15. 怎么判断两个多项式互素_多项式互素的等价条件
  16. win7总是显示加载计算机,win7系统打开“此电脑”很慢总是在加载不显示的具体办法...
  17. Excel方向键无法移动单元格/scrollLock解锁方法
  18. 微信公共平台 “token验证失败”的一个原因
  19. 手机设备唤醒计算机,手机遥控电脑开机神器!局域网唤醒App
  20. 谷歌浏览器怎么拦截网页广告 5步解决广告困扰

热门文章

  1. 辩论赛取胜,进入决赛
  2. 《哈尔滨万佳投资股份有限公司官网建设方案》
  3. 有限公司与无限公司及股份公司概念
  4. AutoCAD2016二次开发创建Polyline包围面Polygon
  5. 国家电网计算机知识点归纳,国家电网知识点
  6. 什么?你还在花一两万学Java,快来看看小白学习java全路线吧
  7. Fate单机部署(主机版本)
  8. linux 如何让.开头的文件不隐藏_如何在Mac上显示隐藏文件?苹果mac显示隐藏文件夹方法
  9. 10家银行大数据岗面试经历,已拿offer,解决北京户口
  10. 把Ubuntu系统装进移动硬盘