匿名对象被弱引用后提前回收的问题
- 背景:做语音搜索项目时,发现一个bug,第一次进入语音页,网络断开和连接,都会走正常的回调,显示不同的网络连接状态。但在此之后,网络异常状态似乎失效了(不能正常显示网络异常的提示)
我的代码如下(大概意思):
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
| object Utils { private var errorStatusListenerRef: WeakReference<ErrorStatusListener>? = null private val errorStatusListener: ErrorStatusListener? get() = errorStatusListenerRef?.get() interface ErrorStatusListener { fun onNetworkConnected() fun onNetworkDisconnected() } fun setErrorStatusListener(listener: ErrorStatusListener?) { errorStatusListenerRef = if (listener != null) WeakReference(listener) else null } fun printErrorStatus(tag: String) { println("[$tag] errorStatusListenerRef: $errorStatusListenerRef") println("[$tag] errorStatusListener: $errorStatusListener") errorStatusListener?.onNetworkDisconnected() errorStatusListener?.onNetworkConnected() println("----------------------------------------") } }
|
这里使用WeakReference主要是为了防止内存泄漏,因为这个类是一个object,作用域是全局,当它的变量持有Activity实例时,如果该实例需要被销毁,但所持有的变量又没有设置WeakReference,就不会被GC回收从而导致内存泄漏。
代码还是很简单的,本质就是设置了个弱引用监听器,调用了回调方法,具体的回调实现可以由调用Utils.setErrorStatusListener方来实现。但断点却发现,除第一次外,printErrorStatus方法中的errorStatusListener都为null,这里应该就是不生效的根本原因了。
来看一下调用方是怎么处理的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| fun main() { Utils.setErrorStatusListener(object : Utils.ErrorStatusListener { override fun onNetworkConnected() { println("匿名对象:网络已连接") } override fun onNetworkDisconnected() { println("匿名对象:网络已断开") } }) Utils.printErrorStatus("GC前") println("触发GC...") System.gc() Thread.sleep(100) Utils.printErrorStatus("GC后") }
|
打印的结果如下:

可以清晰的看到,errorStatusListener被GC回收调了导致它为null。从这里其实我们可以得出结论:
仅被弱引用持有的对象会被 GC 回收,导致弱引用的get()方法返回null
匿名对象的生命周期:
匿名对象的生命周期完全依赖于 “是否有强引用持有它”:
- 创建时:匿名对象被作为参数传递给 setErrorStatusListener 方法。
- 存储时:setErrorStatusListener 将其包装到 WeakReference 中(errorStatusListenerRef = WeakReference(匿名对象))。
注意:WeakReference 本身不会持有强引用,它仅 “弱引用” 这个匿名对象,不影响 GC 回收。
- 函数结束后:printErrorStatus() 函数执行完毕,没有任何变量(如类的属性、局部变量)持有这个匿名对象的强引用(强引用是指直接用 val/var 声明的引用,如 val listener = object : …)。
此时,匿名对象唯一的引用是 WeakReference,而弱引用无法阻止 GC 回收。
- GC 触发时:当 GC 运行时,会发现这个匿名对象没有任何强引用,就会将其回收。此时 errorStatusListenerRef.get() 就会返回 null。
- 怎么解决这个问题呢?很简单,就是让强引用(val/var)去持有这个“匿名对象”,然后将这个强引用赋值给errorStatusListenerRef,让它持有即可,具体代码如下:
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
| fun main() { val statusListener = object : Utils.ErrorStatusListener { override fun onNetworkConnected() { println("匿名对象:网络已连接") } override fun onNetworkDisconnected() { println("匿名对象:网络已断开") } } Utils.setErrorStatusListener(statusListener) Utils.printErrorStatus("GC前") println("触发GC...") System.gc() Thread.sleep(100) Utils.printErrorStatus("GC后") println("主动设置listener为null,再次释放") Utils.setErrorStatusListener((null)) System.gc() Thread.sleep(100) Utils.printErrorStatus("主动释放") }
|
