每日一问:浅谈 onAttachedToWindow 和 onDetachedFromWindow

10,727 阅读4分钟

基本上所有 Android 开发都会接触到 onCreate()onDestory()onStart()onStop() 等这些生命周期方法,但却不是所有人都会去关注到 onAttachXXX() 这样的方法群体,今天,笔者就希望用简短的文章对此进行一定讲解。

Activity 中的 onAttachedToWindow

首先在 Activity 中我们可以重写 onAttachedToWindow()onDetachedFromWindow() 这一对方法。顾名思义,"Attached" 就是附加的意思,所以我们可以确定 onAttachedToWindow() 就是在 View 附加到 window 上的时候进行回调,而 onDetachedFromWindow() 就刚好相反。

这一对方法会在我们熟悉的 Activity 生命周期的 onResume()onPause() 中间,但并不是每一次 onResume()onPause() 回调的时候都会在接下来回调它们。应该比较好理解,我们当然不需要频繁往 window 中附加和分离 View 嘛。

这里自然我们容易产生一个问题,在 onAttachedToWindow() 回调的时候,我们能拿到 View 的宽高么?换句话说就是这时候 View 是否已经经过了测量和绘制呢?

我们编写一个 Demo 进行日志打印看看。

可以看到,并没有。我们只有在 onWindowFocusChanged 回调的时候才能真正的拿到 View 的宽高值。

所以,ActivityonAttachedToWindow() 回调之后,布局中的 View 会回调 onAttachedToWindow() ,然后才会去进行测量和绘制等。那么我们要获取一个 View 的宽高就最好是 View.post() 了。

View 的 onAttachedToWindow 使用场景

ViewonAttachedToWindow() 的调用时机会发生在 onMeasure() 之前,那么它们到底有什么使用场景呢?

我们在自定义 View 的时候,某些比较重量级的资源,而且不能与其他 View 通用的时候,就可以重写这两个方法,并在 onAttachedToWindow() 中进行初始化,onDetachedFromWindow() 方法里释放掉。

比如 Bitmap,虽说现在不用主动调用 recycle() 方法来回收,但在 8.0 及以上系统,手动调用是会立即释放所占用的内存的,所以个人认为还是有必要手动回收的,当然了,如果图片比较小,对内存没什么影响的就不用了。

再比如一些用作计算的子线程,或其他跟 View 显示有关的任务,在 onDetachedFromWindow() 中也可以停掉了,因为大多数情况下,这些实时数据对于被分离后 View 已经没有意义了。

RecyclerView.Adapter 的 onViewAttachedToWindow

细心的小伙伴会发现,在 RecyclerView.Adapter 中也会有这么一个 onViewAttachedToWindow()onViewDetachedToWindow()

这两个方法在列表布局的时候,用作曝光埋点非常好用,当 Adapter 创建的 View 被窗口分离(即滑动离开了当前窗口界面的)的时候,onViewAttachedToWindow() 会被直接回调,反之,在列表项 View 在被滑动进屏幕的时候,onViewDetachedToWindow() 会立马被调用。

有了这样的属性,对于我们的曝光埋点,就手到擒来了,直接在里面做就完事儿了。

RecyclerView.Adapter 咋还有一个 onAttachedToRecyclerView

说到 AdapteronViewAttachedToWindow,咋发现这里面竟然还有一个 onAttachedToRecyclerView 方法,根据源码我们可以发现,onAttachedToRecyclerView() 是在 setAdapter() 的时候触发。

对比一下,我们便能得出以下它们的使用场景:

  1. RecyclerView 本质上也是一个 ViewGroup,那么它的 Item 要显示出来,自然需要 addView() 进来,移出的时候,当然也要 removeView() 出去,所以对应的自然是 onViewAttachedToWindow()onViewDetachedToWindow() 了。所以在一定场景下,可以通过这两个回调来处理一些 Item 移出屏幕,移进屏幕锁需要的工作。为什么说一定场景下呢,因为如果调用了 notifyDataSetChanged() 方法的话,会触发当前在屏幕中的所有 Item 的 onViewAttachedToWindow()
  2. onAttachedToRecyclerViewonAttachedToRecyclerView() 的话,就更加适合做一些资源回收的工作啦。

我的 RecyclerView.Adapter 的 onViewAttachedToWindow 为啥没起作用?

可能会有小伙伴会遇到这个问题,在遇到这个问题前,先检查一下你这个 RecyclerView 是否是一个正常滚动的 View,你如果是被别人嵌套滚动,把自己设置了 isNestedScrollingEnabled 为 false 的话,那你都失去了 Recyclerview 的功用了,那自然是不行的。

可能又有小伙伴说了,由于需求历史原因,我就是用了 NestedScrollView 嵌套了 Recyclerview,并禁掉了 Recyclerview 的滑动功能,但又想做上面的曝光埋点功能,那如何是好?

如果是这样的话,大概你就只能通过类似 ViewgetGlobalVisibleRect() 这样的方法来判断 View 的可见性来处理了。关于 View 的可见性分析,这里就点到为止,大家就自行 Google 吧。