匿名对象被弱引用后提前回收的问题

  • 背景:做语音搜索项目时,发现一个bug,第一次进入语音页,网络断开和连接,都会走正常的回调,显示不同的网络连接状态。但在此之后,网络异常状态似乎失效了(不能正常显示网络异常的提示)

我的代码如下(大概意思):

kotlin 复制代码
object Utils {
    // 弱引用持有监听器
    private var errorStatusListenerRef: WeakReference<ErrorStatusListener>? = null
 
    // 间接获取监听器(通过弱引用的get()方法)
    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")
        // 尝试调用监听器方法(若不为null则执行)
        errorStatusListener?.onNetworkDisconnected()
        errorStatusListener?.onNetworkConnected()
        println("----------------------------------------")
    }
}

这里使用WeakReference主要是为了防止内存泄漏,因为这个类是一个object,作用域是全局,当它的变量持有Activity实例时,如果该实例需要被销毁,但所持有的变量又没有设置WeakReference,就不会被GC回收从而导致内存泄漏。

代码还是很简单的,本质就是设置了个弱引用监听器,调用了回调方法,具体的回调实现可以由调用Utils.setErrorStatusListener方来实现。但断点却发现,除第一次外,printErrorStatus方法中的errorStatusListener都为null,这里应该就是不生效的根本原因了。 来看一下调用方是怎么处理的:

kotlin 复制代码
fun main() {
    // 步骤1:设置匿名对象作为监听器(仅被弱引用持有,无强引用)
    Utils.setErrorStatusListener(object : Utils.ErrorStatusListener {
        override fun onNetworkConnected() {
            println("匿名对象:网络已连接")
        }
 
        override fun onNetworkDisconnected() {
            println("匿名对象:网络已断开")
        }
    })
 
    // 步骤2:第一次打印(此时匿名对象未被GC,弱引用可获取)
    Utils.printErrorStatus("GC前")
 
    // 步骤3:触发GC
    println("触发GC...")
    System.gc() // 建议GC(JVM不保证立即执行,仅为提示)
    Thread.sleep(100) // 等待GC执行(增加回收可能性)
 
    // 步骤4:第二次打印(此时匿名对象已被GC回收,弱引用get()返回null)
    Utils.printErrorStatus("GC后")
}

打印的结果如下: 可以清晰的看到,errorStatusListener被GC回收调了导致它为null。从这里其实我们可以得出结论:

仅被弱引用持有的对象会被 GC 回收,导致弱引用的get()方法返回null

匿名对象的生命周期:

匿名对象的生命周期完全依赖于 "是否有强引用持有它":

  1. 创建时:匿名对象被作为参数传递给 setErrorStatusListener 方法。
  2. 存储时:setErrorStatusListener 将其包装到 WeakReference 中(errorStatusListenerRef = WeakReference(匿名对象))。 注意:WeakReference 本身不会持有强引用,它仅 "弱引用" 这个匿名对象,不影响 GC 回收。
  3. 函数结束后:printErrorStatus() 函数执行完毕,没有任何变量(如类的属性、局部变量)持有这个匿名对象的强引用(强引用是指直接用 val/var 声明的引用,如 val listener = object : ...)。 此时,匿名对象唯一的引用是 WeakReference,而弱引用无法阻止 GC 回收。
  4. GC 触发时:当 GC 运行时,会发现这个匿名对象没有任何强引用,就会将其回收。此时 errorStatusListenerRef.get() 就会返回 null。
  • 怎么解决这个问题呢?很简单,就是让强引用(val/var)去持有这个"匿名对象",然后将这个强引用赋值给errorStatusListenerRef,让它持有即可,具体代码如下:
kotlin 复制代码
fun main() {
    val statusListener = object : Utils.ErrorStatusListener {
        override fun onNetworkConnected() {
            println("匿名对象:网络已连接")
        }
 
        override fun onNetworkDisconnected() {
            println("匿名对象:网络已断开")
        }
    }
    // 步骤1:设置匿名对象作为监听器(仅被弱引用持有,无强引用)
    Utils.setErrorStatusListener(statusListener)
 
    // 步骤2:第一次打印(此时匿名对象未被GC,弱引用可获取)
    Utils.printErrorStatus("GC前")
 
    // 步骤3:触发GC
    println("触发GC...")
    System.gc() // 建议GC(JVM不保证立即执行,仅为提示)
    Thread.sleep(100) // 等待GC执行(增加回收可能性)
 
    // 步骤4:第二次打印(由于statusListener是强引用,所以此时的GC不会对该变量进行回收,除非主动设置为null,然后进行释放)
    Utils.printErrorStatus("GC后")
 
    println("主动设置listener为null,再次释放")
    Utils.setErrorStatusListener((null))
    System.gc() // 建议GC(JVM不保证立即执行,仅为提示)
    Thread.sleep(100) // 等待GC执行(增加回收可能性)
    Utils.printErrorStatus("主动释放")
}
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui