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

  • 背景:做语音搜索项目时,发现一个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("主动释放")
}
相关推荐
zh_xuan4 小时前
Android Looper源码阅读
android
用户02738518402614 小时前
[Android]RecycleView的item用法
android
前行的小黑炭15 小时前
Android :为APK注入“脂肪”,论Android垃圾代码在安全加固中的作用
android·kotlin
帅得不敢出门16 小时前
Docker安装Ubuntu搭建Android SDK编译环境
android·ubuntu·docker
tangweiguo0305198716 小时前
Android Kotlin 动态注册 Broadcast 的完整封装方案
android·kotlin
fatiaozhang952716 小时前
浪潮CD1000-移动云电脑-RK3528芯片-2+32G-安卓9-2种开启ADB ROOT刷机教程方法
android·网络·adb·电脑·电视盒子·刷机固件·机顶盒刷机
前行的小黑炭17 小时前
Android 不同构建模式下使用不同类的例子:如何在debug模式和release模式,让其使用不同的类呢?
android·kotlin·gradle
andyguo18 小时前
AI模型测评平台工程化实战十二讲(第一讲:从手工测试到系统化的觉醒)
android
2501_9159214318 小时前
小团队如何高效完成 uni-app iOS 上架,从分工到工具组合的实战经验
android·ios·小程序·uni-app·cocoa·iphone·webview
幂简集成18 小时前
通义灵码 AI 程序员低代码 API 课程实战教程
android·人工智能·深度学习·神经网络·低代码·rxjava