为什么View的 onDetachedFromWindow 方法不一定执行

278 篇文章 5 订阅
92 篇文章 2 订阅

屏幕截图 2024-06-04 084118.png

在开发过程中,发现一个偶现的内存泄漏问题,经排查发现是 onDetachedFromWindow 没有执行造成的。项目背景是一个自定义的头像控件内部需要监听头像变更,因此在创建 View 时注册了监听,然后在 onDetachedFromWindow 时取消注册。伪代码如下:

class UserAvatarImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) :
    AppCompatImageView(context, attrs, defStyleAttr){

    init {
        // 注册监听用户头像的变更
        registerAvatarChangeListener()
    }


    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        // 取消监听用户头像的变更
        unregisterAvatarChangeListener()
    }
}

可以看到,只有当 onDetachedFromWindow 没有执行的时候才会发生内存泄漏。那为什么 onDetachedFromWindow 没有执行呢,这个只能从源码中找答案了。

onDetachedFromWindow 的源码如下所示:

// View 的 onDetachedFromWindow 方法,内部是空实现
protected void onDetachedFromWindow() {  
}

// onDetachedFromWindow 是被 dispatchDetachedFromWindow 内部调用
void dispatchDetachedFromWindow() {
    ...

    onDetachedFromWindow();
    onDetachedFromWindowInternal();

    ...
}

从源码中可以看到,View 的 onDetachedFromWindow 方法其内部是由 View 的 dispatchDetachedFromWindow 调用的。那 dispatchDetachedFromWindow 又是在哪里被调用的呢?实际上,它是在 ViewRootImpl 中被调用的,这个涉及到了,Android View的绘制流程

ViewRootImpl 的相关源码如下:

void dispatchDetachedFromWindow() {
    ...
    // 只有当 mView 和 mView.mAttachInfo 不为 null 时,dispatchDetachedFromWindow 才会被调用
    if (mView != null && mView.mAttachInfo != null) {
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
        // dispatchDetachedFromWindow 方法在这里被调用
        mView.dispatchDetachedFromWindow();
    }

    ...
}

通过源码可以看到,dispatchDetachedFromWindow 方法是否被调用是根据  mView 和 mView.mAttachInfo 是否为 null 来判断的。那么  mView 和 mView.mAttachInfo 又是什么时候创建的呢?

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
        if (mView == null) {
            // 在 ViewRootImpl 的 setView 中会给 mView 赋值
            mView = view;
            ...
    }
}

从源码中可以看到,mView 属性会在 ViewRootImpl 的 setView 方法中赋值。而 setView 方法是在什么时候被调用的呢?其实 setView 是在 onResume 方法之后,内部流程是通过 PhoneWindow 获取 WindowManagerImpl 来调用 addView 方法,其内部会调用 WindowManagerGlobal.addView 方法,最后调到 ViewRootImpl 的 setView 方法。

至此内存泄漏的原因就清楚了,这是因为 onAttachedToWindow 是在 onResume 后执行的,因此如果在 onResume 之前如果 finish activity 就会导致 ViewRootImpl 的 setView 方法不会被执行。由于 mView 属性为 null,activity 在 onDestroy 后也不执行 onDetachedFromWindow 方法,在View 中的 unregisterAvatarChangeListener 就无法执行了,这就造成了内存泄漏的问题。

解决方案很简单,就是不再创建 View 时创建监听,而是在 onAttachedToWindow 时监听。代码示例如下:

class UserAvatarImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) :
    AppCompatImageView(context, attrs, defStyleAttr){
        
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        // 在 onAttachedToWindow 中注册监听用户头像的变更
        registerAvatarChangeListener()
    }
    

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        // 取消监听用户头像的变更
        unregisterAvatarChangeListener()
    }

}

为什么在 onAttachedToWindow 时监听就可以了,这是因为 onAttachedToWindow 是在 setView 方法之后执行的。onAttachedToWindow 方法可以确保调用它时, mView 属性不为 null,即在 activity  onDestroy 时,onDetachedFromWindow 方法一定会被调用。

总结一下就是,onDetachedFromWindow 和 onAttachedToWindow 两个方法是配对的。如果 onAttachedToWindow 没有被执行,那么 onDetachedFromWindow 也不会被执行。在开发过程中一定要注意,onDetachedFromWindow 和 onAttachedToWindow 两个方法是可能不执行的。最好将把这两个方法配套使用,不然就有可能产生我这样的内存泄漏的问题。

为什么View的 onDetachedFromWindow 方法不一定执行

作者:小墙程序员
链接:https://juejin.cn/post/7383264553720905763
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值