弱引用回调引发的坑

news2024/10/5 16:22:18

在开发中,常常会用到回调模型,为了避免回调监听未被主动释放,导致内存泄露,我们会用到 WeakReference 来存放回调引用,然而要注意的是回调类被回收的坑。本文记录笔者开发中遇到弱引用回调被回收的坑及思考。

奇怪的现象

平常的一天,像往常一样敲着项目代码,今天要完成的需求是为我们的自定义 View 添加一个回调,当用户操作自定义 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
public class MyView extends LinearLayout {

    // 回调引用
    private OnItemSelectedListener mListener;
    
    // 回调接口定义
    public interface OnItemSelectedListener {
        void onSelect(String text);
    }

    // 设置回调
    public void setListener(OnItemSelectedListener listener) {
        mListener = listener;
    }
    
    // 释放回调
    public void dispose() {
        mListener = null;
    }
    
    ...
    
    public void something() {
        ...
        // 回调
        if (mListener != null) {
            mListener.onSelect(text);
        }
    }

    ...
 
}

这时候发现,调用方设置回调后,可能并不会主动调用 dispose() 方法对监听进行释放,所以我们简单优化一下:

使用弱引用代替强引用,这样当调用方的 Listener 被回收时,弱引用会自动被释放掉,不会造成内存泄露:

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
public class MyView extends LinearLayout {

    // 回调弱引用
    private WeakReference<OnItemSelectedListener> mListener;
    
    // 回调接口定义
    public interface OnItemSelectedListener {
        void onSelect(String text);
    }

    // 设置回调
    public void setListener(OnItemSelectedListener listener) {
        mListener = new WeakReference<>(listener);
    }
    
    ...
    
    public void something() {
        ...
        // 回调
        if (mListener != null) {
            // 弱应用取出实例
            OnItemSelectedListener listener = mListener.get();
            if (listener != null) {
                listener.onSelect(text);
            }
        }
    }

    ...
 
}

这样确实没什么问题,WeakReference 并不会强持有引用。

1
2
3
4
5
6
7
8
9
public void initView() {
    ...
    myView.setListener(new MyView.OnItemSelectedListener() {
        @Override
        public void onSelect(String text) {
            ...
        }
    });
}

然而,当这样使用时,发现一个奇怪的现象:某些时候回调的 onSelect() 方法不会被回调,或者是仅仅在初期能够回调,过一会儿就不被回调了。

大胆猜测

没错,很神奇的现象,接下来我们使用调试工具进行一步步调试,发现更神奇,listener 竟然为 null,如图:

WX20180503-201328@2x.png

弱引用什么时候才会为 null 呢?

WX20180503-202013@2x.png

源码中的文档已经告诉我们,当被引用的实例被 GC 回收的时候会返回 null,而且关于 referent 变量的状态是由虚拟机特殊对待的:

1
2
3
4
5
6
7
8
9
public abstract class Reference<T> {

    private T referent;         /* Treated specially by GC */

    volatile ReferenceQueue<? super T> queue;
    
    ...
        
}

那么,可以猜想到为什么会出现这样的情况,就是:我们的匿名内部类被 GC 回收掉了。

具体而言,对于 new 出来的 OnItemSelectedListener 实例只有 MyView 中有一个弱引用对其引用,而不存在任何一个强引用对其引用,这样当 GC 到来时,就会将其标记为即将被回收的对象,并排队执行 finalize() 方法,然后很快在下一次 GC 到来时将其回收。

这样一来,也就解释了为什么刚开始能正常工作,之后 listener 一直为 null 了。

实验证实

刚刚只是进行一个猜测,下面来做一个实验验证一下我们的想法。

(1)声明一个回调接口

1
2
3
public interface Callback {
    void call();
}

(2)我们的测试类

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
public class InnerClassGc {

    public WeakReference<Callback> reference;

    public void fun() {

        // 匿名内部类
        Callback callback = new Callback() {

            @Override
            public void call() {
                // do something
            }

            @Override
            protected void finalize() throws Throwable {
                super.finalize();
                // 监控被垃圾回收
                System.out.println("base finalize()");
            }
        };

        reference = new WeakReference<>(callback);

    }

}

这里的回调 Callback 中,重写了 finalize() 方法,该方法将在实例被垃圾回收时调用,这里能方便我们看实例是否被回收

(3)测试

首先测试首次正常的情况:

1
2
3
4
5
6
7
// 实例化
InnerClassGc innerClassGc = new InnerClassGc();
// 调用
innerClassGc.fun();
// 检查是否被回收
Callback callback = innerClassGc.reference.get();
System.out.println(callback);

此时,即便是弱引用,但没有发生垃圾回收情况,所以 callback 局部变量没有被回收,运行结果如下:

WX20180503-205729@2x.png

接下来模拟存在垃圾回收的情况,我们手动调用 System.gc() 来触发诱导 JVM 进行垃圾回收:

1
2
3
4
5
6
7
8
9
// 实例化
InnerClassGc innerClassGc = new InnerClassGc();
// 调用
innerClassGc.fun();
// ***** 触发gc *****
System.gc();
// 检查是否被回收
Callback callback = innerClassGc.reference.get();
System.out.println(callback);

再看运行结果:

WX20180503-210345@2x.png

结果正如所想,在脱离函数的局部作用域后,强引用失效,垃圾回收将不存在其它强引用的 callback 实例回收了,导致弱引用 get() 为 null

进一步思考

到了这里,可能读者已经明白如何解决这个问题了,在函数内部的变量会被垃圾回收,如果将它移到类成员变量级别,类成员变量级的强引用在类销毁的时候才会失效。在这之前的整个过程,由于强引用的存在,实例不会被回收,弱应用 WeakReference 也将一直有数据,故最容易的解决方案就是指定一个类成员变量强引用它:

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
public class InnerClassGc {

    private WeakReference<Callback> reference;

    private Callback callback;

    private void fun() {

        // 类成员变量赋值
        callback = new Callback() {

            @Override
            public void call() {
                // do something
            }

            @Override
            protected void finalize() throws Throwable {
                super.finalize();
                System.out.println("base finalize()");
            }
        };

        reference = new WeakReference<>(callback);

    }

}

执行结果如下:

WX20180503-212007@2x.png

总体来说,弱引用其实就是不额外增加强引用的情况下,能够取得类的实例,可以帮助我们避免许多容易引起内存泄露的情况,但在使用的过程中仍需小心。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1006280.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

删除的快捷键ctrl加什么?快捷键删除的文件怎么恢复

在许多情况下&#xff0c;我们可以利用电脑的快捷键&#xff0c;即键盘上的组合按键&#xff0c;来执行特定的操作&#xff0c;例如使用组合键删除文件。当我们熟练掌握这些快捷键时&#xff0c;能够显著提升工作效率。那么&#xff0c;你知道ctrl哪个键可以删除文件吗&#xf…

花西子口碑下降,国产化妆品应该如何增加自己的品牌曝光

过去几天&#xff0c;因为花西子一支眉笔克重单价比大牌还贵&#xff0c;而某直播间的主播在面对消费者的质疑时&#xff0c;没有选择解答用户的疑惑&#xff0c;而是质疑消费者的工作能力等等&#xff0c;导致花西子的口碑下降&#xff0c;而主播的印象也逐渐负面&#xff0c;…

【四】3D Object Model之创建Creation——read_object_model_3d()算子

&#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; Halcon算子太多&#xff0c;学习查找都没有系统的学习查找路径&#xff0c;本专栏主要分享Halcon各类算子含义及用法&#xff0c;有…

一、Mediasoup源码介绍

一、Mediasoup 整体结构 整个Mediasoup库通过Nodejs管理&#xff0c;比如整体逻辑、worker、router、producer、consumer...都是通过JS进行管理的。 其底层的数据传输是通过C部分进行控制的&#xff0c;通过NodeJs来控制C部分&#xff0c;以实现整体的数据传输效 二、Mediasou…

【C语言】指针详解(3)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解指针(2)&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一.函数指针数组二.指向函数指针数组的指针&#xff08;不重要&#xff09;三.回调函数 一.函…

9.13 | day 6 |day 45| to 完全平方数

● 70. 爬楼梯 &#xff08;进阶&#xff09; class Solution {public int climbStairs(int n) {int[] dp new int[n1];//设置背包容量&#xff1a;n个int m 2;//有两个物品&#xff0c;注意这是一个完全背包问题dp[0] 1;//initialize ​for(int i 1;i<n;i){//遍历背包f…

前缀和思想

何为前缀和 有一个数组a, 为 ...... 前缀和 ...... 有两个问题: 1.如何求? 只需要从前往后遍历,令 就可以了,最开始是 ,定义 0 2. 有什么用? 能够快速地求出原数组中某一段的和,预处理的…

利用procdump+Mimikatz绕过杀软获取Windows明文密码

利用procdumpMimikatz绕过杀软获取Windows明文密码 1.原理2.实操部分 1.原理 Mimikatz是从lsass.exe中提取明文密码的&#xff0c;当无法在目标机器上运行Mimikatz时&#xff0c;我们可使用ProcDump工具将系统的lsass.exe进程进行转储&#xff0c;导出dmp文件&#xff0c;拖回…

计算机网络工程毕业设计题目选题大全

文章目录 0 简介1 如何选题2 最新网络工程选题2.1 Java web - SSM 系统2.2 大数据方向2.3 人工智能方向2.4 其他方向 4 最后 0 简介 学长搜集分享最新的网络工程专业毕设毕设选题&#xff0c;难度适中&#xff0c;适合作为毕业设计&#xff0c;大家参考。 学长整理的题目标准…

windows 下使用virtualbox7.0设置共享文件夹详细,绝对好用

1、打开virtualbox软件 &#xff0c;点击设置-》存储-》控制器-》新建虚拟光驱&#xff0c;如下图所示 2、 重启virtualbox&#xff0c;重启方式可使用 enter键方式&#xff0c;会弹出一个小窗口&#xff0c;在小窗口中点击 设备-》安装增强功能 点击后&#xff0c;进入linux…

万字总结线程安全问题

目录 1. 线程安全 1.1 线程不安全的原因 1&#xff09;修改共享数据 2&#xff09;原子性 2&#xff09;可见性 2. synchronized 关键字-监视器锁 monitor lock 2.1 synchronized 的特性 1&#xff09;互斥 2&#xff09;可重入 2.2 使用 synchronized 解决上面的线程…

阿里张勇“下课” “逍遥子”从此逍遥了

作者&#xff1a;积溪 琥珀消研社快评&#xff1a;他服务过两个首富&#xff0c;曾被称为找工皇帝&#xff0c;如今张勇从干了16年的阿里辞职&#xff0c; 逍遥子从此逍遥了&#xff1f;#阿里 #张勇 #马云 马爸爸曾说过&#xff0c;他天不怕地不怕&#xff0c;就怕CFO当CEO&a…

【Linux网络】TCP/IP三次握手、四次挥手流程

目录 一、三次握手&#xff0c;建立连接 二、四次挥手&#xff0c;断开连接 三、主要字段 1、标志位&#xff08;Flags&#xff09; 2、序号&#xff08;sequence number&#xff09; 3、确认号&#xff08;acknowledgement number&#xff09; 四、三次握手的报文变化 五…

python3如何安装各类库的小总结

我的python3的安装路径是&#xff1a; C:\Users\Administrator\AppData\Local\Programs\Python\Python38 C:\Users\Administrator\AppData\Local\Programs\Python\Python38\python3.exeC:\Users\Administrator\AppData\Local\Programs\Python\Python38\Scripts C:\Users\Admin…

全球视野,共赴“睛”彩!四川眼科医院2023全国眼科学术大会行圆满收官!

9月6日—10日&#xff0c;国内眼科学界最盛大的学术会议——中华医学会第二十七次全国眼科学术大会(CCOS 2023)在湖南长沙隆重举办!逾万名国内外眼科专家、学者代表参加盛会&#xff0c;聚焦眼科发展的新技术、新知识以及新的经验&#xff0c;分享眼科和视觉科学方面最新的研究…

如何在JavaScript中实现链式调用(chaining)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ JavaScript中的链式调用⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴…

JD(商品详情)API接口

为了进行电商平台 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个JD应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载JDAPI的SDK并掌握基本的API基础知识和调用 4&#xff09;利用SDK接口和…

肖sir__mysql之介绍__001

mysql之介绍 一、认识数据库 &#xff08;1&#xff09;什么是数据库&#xff1f; 是存放数据的电子仓库。以某种方式存储百万条&#xff0c;上亿条数据&#xff0c;供多个用户访问共享。 如&#xff1a; &#xff08;2&#xff09;数据库分关系型数据库和非关系型数据库 a、…

互联网行业:是走下坡路还是瘦死的骆驼比马大?看看这个你就知道了!

随着互联网行业的快速发展&#xff0c;一些人开始质疑这个行业是否已经开始走下坡路了。 但是&#xff0c;我们想说的是&#xff0c;互联网行业还远远没有达到饱和状态&#xff0c;它仍然是一个充满机遇和挑战的领域。 让我们来看一些数据。根据最新的统计数据显示&#xff0c…

第二证券:低位放量下跌是什么征兆?

近年来&#xff0c;股市的波动一直是人们重视的热点。低位放量跌落是出资者最不愿意看到的&#xff0c;可是却经常呈现。那么&#xff0c;低位放量跌落是什么预兆呢&#xff1f;从多个角度剖析一下原因&#xff0c;帮助出资者更好地了解商场动态。 从技能面剖析&#xff0c;低…