题目
在Android中实现线程安全且内存安全的Kotlin单例,支持延迟初始化与配置变化恢复
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
单例模式实现,线程安全,内存泄漏预防,配置变化处理,Kotlin特性应用
快速回答
实现要点:
- 使用
by lazy或companion object实现延迟初始化 - 结合
@Volatile和双重检查锁定保证线程安全 - 通过
Application上下文避免内存泄漏 - 处理配置变化时的实例重建问题
- 利用
LazyThreadSafetyMode优化性能
核心需求与挑战
在Android中实现单例需解决:1) 线程安全初始化 2) 避免持有Activity上下文导致内存泄漏 3) 配置变化(如屏幕旋转)时的行为一致性。
基础实现方案
class Singleton private constructor(context: Context) {
companion object {
@Volatile private var instance: Singleton? = null
fun getInstance(context: Context): Singleton {
return instance ?: synchronized(this) {
instance ?: Singleton(context.applicationContext).also { instance = it }
}
}
}
// 使用Application Context
private val appContext = context.applicationContext
// 示例资源
private val resources = appContext.resources
}关键优化点
- 线程安全:双重检查锁定(DCL)配合
@Volatile确保单例创建原子性 - 内存安全:存储
applicationContext而非Activity上下文 - 延迟初始化:首次调用
getInstance()时创建实例
使用Lazy的高级实现
class Singleton private constructor(context: Context) {
companion object {
private val lock = Any()
val instance: Singleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
// 需从外部传入Context,通常通过init函数
error("Context must be initialized first")
}
// 初始化入口
fun init(context: Context) {
synchronized(lock) {
if (instance != null) return
instance = Singleton(context.applicationContext)
}
}
}
}配置变化处理方案
问题:屏幕旋转导致Activity重建时可能重复初始化
解决方案:
- 在Application类中初始化单例
- 使用
ViewModel保存实例(结合Dagger/Hilt依赖注入更佳) - 添加重建标志位:
private var initialized = false fun init(context: Context) { if (initialized) return synchronized(lock) { if (!initialized) { instance = Singleton(context.applicationContext) initialized = true } } }
最佳实践
- 上下文管理:始终使用
applicationContext - 依赖注入:通过Dagger/Hilt管理单例生命周期
- 异常处理:添加
init状态检查避免未初始化调用 - 性能优化:
LazyThreadSafetyMode.PUBLICATION允许多线程初始化(当初始化成本低时)
常见错误
- 内存泄漏:存储Activity上下文 → 解决方案:
// 错误示例 class Singleton(private val activity: Activity) // 正确做法 private val appContext: Context = context.applicationContext - 空指针异常:未初始化就调用 → 添加状态检查:
fun getData(): String { check(initialized) { "Singleton not initialized!" } // ... } - 线程竞争:未使用同步机制 → 使用
synchronized或原子引用
扩展知识
- Kotlin对象声明:
object Singleton天然线程安全但无法延迟初始化 - Dagger作用域:
@Singleton注解配合Component实现容器管理 - ViewModel+LiveData:替代单例实现配置无关的数据持有
- 性能对比:
by lazyvs 双重检查锁定(DCL在多次访问时性能更优)