You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Android中Object内懒加载类实例在应用杀死后仍留存的问题

问题原因分析

1. 进程级单例未被销毁

DataSource是Kotlin的object单例,属于进程级生命周期——只要应用进程没有被系统彻底杀死,这个单例就会一直存在于内存中。你调用的finishAndRemoveTask()只是关闭了所有Activity,但Android系统为了优化应用重启速度,经常会将进程保留在后台缓存中。此时DataSource内部懒加载的ServerCalls实例也会持续留存,其token变量自然还是前一个用户的旧值。

2. Token仅初始化时读取一次

ServerCalls的token变量是在类初始化时从SharedPreferences读取的,之后不会自动同步SP的变化。即使登出时清空了SP,只要ServerCalls实例没被销毁,token就会保持旧值,新用户登录后也不会自动更新。

3. 懒加载的特性导致实例复用

_server通过by lazy初始化,一旦首次创建完成,后续所有调用都会复用这个实例,不会重新执行初始化逻辑——也就不会重新读取SP中的新token值。


解决方案

方案1:每次请求时直接读取最新Token(最稳妥)

修改ServerCalls,去掉存储token的变量,每次需要时直接从SharedPreferences读取:

class ServerCalls(private val authApi: AuthApi, private val application: Application) {
    // 每次使用时实时读取SP中的token
    private val token get() = application.sharedPreferences.tempAuth

    fun fetchData(): Response<Mountains> {
        return authApi.fetchData(token)
    }
}

这样不管ServerCalls实例是否复用,每次请求都会获取最新的SP值,从根源避免token残留问题。

方案2:登出时显式重置单例状态

将DataSource中的懒加载实例改为可重置的类型,在登出时主动重置,确保下次使用时重新初始化:

object DataSource {
    private lateinit var application: Application

    fun init(application: Application) {
        this.application = application
    }

    // 改用lateinit var替代lazy,支持重置
    private lateinit var _server: ServerCalls

    private val server: ServerCalls
        get() {
            if (!::_server.isInitialized) {
                _server = ServerCalls(AuthApi, application)
            }
            return _server
        }

    // 新增重置方法,登出流程中调用
    fun reset() {
        if (::_server.isInitialized) {
            // 直接重置token为SP的最新值(此时SP已清空)
            _server.token = application.sharedPreferences.tempAuth
        }
    }

    fun fetchData(): Response<Mountains> = server.fetchData(server.token)
}

登出时调用DataSource.reset(),确保ServerCalls的token被更新为SP清空后的值。

方案3:改用依赖注入管理实例(长期最佳实践)

使用Dagger Hilt、Koin等依赖注入框架,将ServerCalls的生命周期与用户会话绑定:

  • 创建用户会话级别的组件,用户登录时注入全新的ServerCalls实例(读取此时SP中的新token);
  • 用户登出时销毁该会话组件,确保旧实例被回收。
    这种方式从架构层面避免了进程级单例带来的状态残留问题,更符合Android应用的生命周期设计。

方案4:应急方案——强制杀死进程(不推荐)

如果需要临时解决问题,可以在登出时强制杀死应用进程:

android.os.Process.killProcess(android.os.Process.myPid())

但这种方式不符合Android最佳实践,会导致应用重启变慢,且系统可能自动拉起进程,不建议长期使用。


内容的提问来源于stack exchange,提问作者Hmerman6006

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 03:55:28