第3章 创建 Preferences DataStore 实例:属性委托与单例模式

3.1 原理概述

Android 官方推荐的 Preferences DataStore 创建方式基于 属性委托(Property Delegation),通过 Kotlin 的 by 关键字将 DataStore<Preferences> 实例的创建与生命周期绑定到 Context 的扩展属性上。这种方式既简化了样板代码,又保证了实例的惰性初始化和内存安全性。

同时,为避免应用中多个组件重复创建 DataStore 实例导致文件竞争和资源浪费,必须采用 单例模式 设计。结合属性委托,通常将 DataStore 实例声明为顶层扩展属性,或置于 companion object 内,并通过 by lazy 确保全局唯一性。

关键的构造参数 context 和 fileName 决定了两件事:

  • context:必须使用 Application Context(而非 Activity/Service 上下文),防止因组件销毁导致 DataStore 持有过期引用。
  • fileName:偏好设置文件在内部存储中的名称(不含路径),需保持唯一且可读,建议使用小写字母和下划线,如 user_preferences。

3.2 实现流程

  1. 在模块级 build.gradle.kts 中添加 DataStore 依赖(假设已添加,见第2章)。
  2. 定义顶层扩展属性或单例对象,使用 by preferencesDataStore(name = "...") 委托。
  3. 将 context 参数传入 PreferenceDataStoreFactory.create 或直接使用预置委托函数(内部已处理上下文)。
  4. 在 Activity / Application / ViewModel 中通过 context.dataStore 访问单例实例。

3.3 代码示例:属性委托与单例模式

// ========== 方案一:顶层扩展属性 + by lazy 实现单例 ==========
// 直接使用官方 preferencesDataStore 委托(内部使用 by lazy)
private val Application.dataStore by preferencesDataStore(name = "user_preferences")

// 若需自定义 factory,可替换为:
val Application.dataStore: DataStore<Preferences> by lazy {
    PreferenceDataStoreFactory.create {
        applicationContext.preferencesDataStoreFile("user_preferences")
    }
}

// ========== 方案二:单例对象托管 ==========
object DataStoreProvider {
    private val appContext: Context by lazy {
        // 建议通过 ContentProvider 或 Application.attachBaseContext 获取全局 Context
        // 这里仅演示,实际应注入
        throw NotImplementedError("Inject Application context properly")
    }

    val dataStore: DataStore<Preferences> by lazy {
        PreferenceDataStoreFactory.create {
            appContext.preferencesDataStoreFile("user_preferences")
        }
    }
}

// ========== 推荐写法(官方标准):直接在 Application 类中定义 ==========
class MyApp : Application() {
    companion object {
        val dataStore: DataStore<Preferences> by lazy {
            PreferenceDataStoreFactory.create {
                applicationContext().preferencesDataStoreFile("user_preferences")
            }
        }

        // 用于获取 Application Context 的便捷方法
        private lateinit var instance: MyApp
        fun applicationContext(): MyApp = instance
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

// ========== 在 Activity 中直接通过属性委托访问 ==========
class MainActivity : AppCompatActivity() {
    // 方式一:持有 Application 级别的 DataStore
    private val dataStore = MyApp.dataStore

    // 方式二(如果采用顶层扩展属性):
    // private val dataStore by application.dataStore

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 数据读写操作见后续章节
    }
}

3.4 参数说明与对比

参数类型说明推荐值
contextContext必须为 Application Context,避免内存泄漏applicationContext 或 MyApp.instance
fileNameString内部存储的文件名,不含路径,应全局唯一"user_preferences"
produceFile(工厂参数)(Context) -> File自定义文件路径,默认使用 preferencesDataStoreFile通常保持默认

3.5 调试与验证

在 Application 的 onCreate 中打印 DataStore 实例的哈希码,确认单例唯一性:

// 在 MyApp.onCreate() 中
Log.d("DataStore", "DataStore instance: ${MyApp.dataStore} hash: ${System.identityHashCode(MyApp.dataStore)}")

// 在其他 Activity 中重复调用,应打印相同哈希值

也可通过 Android Studio 的 Device File Explorer 查看 /data/data/your.package.name/files/datastore/ 目录下是否生成了 user_preferences.preferences_pb 文件。

3.6 常见错误与避坑指南

  • ❌ 使用 Activity Context 创建 DataStore:Activity 销毁后 DataStore 可能持有悬空引用,导致崩溃或数据丢失。
    ✅ 始终使用 applicationContext 或 Application 实例。
  • ❌ 每次读写都新建 DataStore 实例:违反单例原则,引发文件锁竞争。
    ✅ 通过属性委托 + by lazy 自动保证全局唯一。
  • ❌ fileName 包含路径或特殊字符:preferencesDataStoreFile 会自动拼接路径,传入路径会导致异常。
    ✅ 仅使用字母数字和下划线,如 "app_settings"。
  • ❌ 在非 UI 线程创建 DataStore:PreferenceDataStoreFactory.create 内部使用 Dispatchers.IO,但若手动初始化在 UI 线程可能增加启动耗时。
    ✅ 惰性初始化自然延后到首次访问,无需刻意处理。

3.7 性能与稳定性优化

  • 使用 Application 级别的单例:避免因 Activity 重建导致 DataStore 重新创建,减少 I/O 开销。
  • 避免频繁调用 preferencesDataStoreFile:该函数每次调用都会检查目录存在性,建议只在惰性初始化时执行。
  • 合理设置文件名:若应用有多个独立偏好设置模块,可创建多个 DataStore(如 "user_prefs" 和 "feature_flags"),但每个 DataStore 文件应独立操作,减少锁争用。
💡 实践建议:将 DataStore 定义在 Application 类的 companion object 中,并暴露一个顶层扩展属性供组件直接调用。例如:
// 在 Application 中
val Context.appDataStore: DataStore<Preferences> by preferencesDataStore(name = "app_settings")

3.8 本章核心流程图:DataStore 单例创建链路

DataStore 单例创建流程图 Application 启动 by preferencesDataStore(name=”…”)) by lazy 触发首次访问 Factory.create { ctx → … } applicationContext 文件 /datastore/xxx.pb DataStore<Preferences> 实例 注:整个过程中,by lazy 保证所有组件共享同一个 DataStore 实例;context 必须为 Application Context 避免使用 Activity Context;fileName 不能包含路径,只能为简单字符串。

3.9 本章小结

通过属性委托 by preferencesDataStore(name = "...") 配合 by lazy 机制,可以优雅地创建全局唯一的 DataStore 实例。关键点包括:

  • 必须传入 Application Context(而非 Activity/Service 上下文)。
  • 文件名使用小写字母和下划线,不含路径。
  • 单例模式由 Kotlin 惰性初始化自然保证,无需手动加锁。
  • 所有数据读写操作都通过同一个实例完成,避免并发文件操作问题。