4. SharedPreferences读取代码现状:原有读取写入代码片段、键类型梳理

在实施迁移之前,必须精确掌握现有项目中所有 SharedPreferences 的读写代码片段、键名约定与值类型。本章将所有典型用法分类归纳,便于后续为每个键设计对应的 DataStore 等价逻辑。

4.1 典型写入代码片段

旧项目普遍通过 SharedPreferences.Editor 进行写入,使用 apply() 或 commit() 提交。以下为 Kotlin 项目中最常见的三种写入模式:

// 模式一:直接写入基本类型
val prefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
prefs.edit()
    .putString("user_name", "张三")
    .putInt("user_age", 28)
    .putBoolean("is_vip", true)
    .putFloat("score", 89.5f)
    .putLong("register_time", System.currentTimeMillis())
    .apply()

// 模式二:写入 JSON 字符串(需要手动序列化)
val gson = Gson()
val address = Address("北京", "朝阳")
prefs.edit()
    .putString("address_json", gson.toJson(address))
    .apply()

// 模式三:写入 Set<String> 用于多选偏好
val tags = setOf("阅读", "运动", "美食")
prefs.edit()
    .putStringSet("user_tags", tags)
    .apply()

4.2 典型读取代码片段

// 基本类型读取(必须提供默认值)
val name = prefs.getString("user_name", "默认用户") ?: "默认用户"
val age = prefs.getInt("user_age", 0)
val isVip = prefs.getBoolean("is_vip", false)
val score = prefs.getFloat("score", 0f)
val registerTime = prefs.getLong("register_time", 0L)

// JSON 字符串反序列化
val rawJson = prefs.getString("address_json", null)
val address = rawJson?.let { gson.fromJson(it, Address::class.java) }

// Set<String> 读取
val tags = prefs.getStringSet("user_tags", emptySet()) ?: emptySet()

4.3 键名与类型全面梳理

将项目中所有 SharedPreferences 键及其关联的类型、默认值和访问频率整理为下表,这是迁移映射的核心依据:

键名 值类型 存储文件 默认值 访问频率 备注
user_name String user_prefs "默认用户" 每次页面加载 无特殊
user_age Int user_prefs 0 偶尔 可能被业务删除
is_vip Boolean user_prefs false 频繁(付费功能判断) 需保持响应速度
score Float user_prefs 0f 保存、读取多次 精度无要求
register_time Long user_prefs 0L 仅显示时 时间戳,迁移后可用 Long
address_json String (JSON) user_prefs null 用户修改地址时 需改为 DataStore 中自定义 Object 类型
user_tags Set<String> user_prefs emptySet() 偶尔 DataStore 中可通过 List<String> 或 Set<String> 存储

4.4 现有代码隐患分析

  • 类型不安全:getString 返回 String?,必须手动处理 null;putFloat 与 getFloat 键名写错时仅返回默认值,无编译期检查。
  • 键名硬编码:字符串常量散落在 Activity、Fragment、ViewModel 中,修改键名需全局搜索替换。
  • 同步风险:多个线程同时读写同一 SharedPreferences 时可能出现数据覆盖或读取到脏数据(除非使用 commit() 阻塞,但性能差)。
  • JSON 序列化开销:对象存储必须手动 Gson 转换,增加代码冗余且容易遗漏 try-catch。

4.5 迁移准备:键类型映射关系

将 SharedPreferences 的原生类型映射到 DataStore 支持的对应 Proto 字段类型(本课程使用 Proto DataStore)或 Preferences DataStore 的键类型:

SharedPreferences 类型 Preferences DataStore 键类型 Proto DataStore 字段类型
String stringPreferencesKey() string / optional string
Int intPreferencesKey() int32 / int64
Boolean booleanPreferencesKey() bool
Float floatPreferencesKey() float / double
Long longPreferencesKey() int64
Set<String> stringPreferencesKey() + 自定义序列化 / 暂不支持直接存储 Set repeated string
JSON 对象 需手动序列化为 String 必须在 .proto 中定义对应 message

重要提示:对于 JSON 存储的复杂对象,强烈建议采用 Proto DataStore,并预定义 message 结构。这样不仅可以获得类型安全的编译期检查,还能避免 Gson 反射带来的性能损耗。本章后续代码实现将基于 Proto 方案。

4.6 键类型与读写流程对比

SharedPreferences 读/写流程 vs DataStore 响应式流程 SharedPreferences put/get 直接访问 apply() 异步提交 可能阻塞 UI (commit) 无数据变化观察 DataStore (Preferences / Proto) Flow 响应式读取 suspend 写入 自动收集数据变化 协程安全,无阻塞 迁移关键:需将 SP 的阻塞型读/写改为 Flow + suspend 的非阻塞型 同时为每个键提供默认值,保证平滑过渡

实施建议:在正式开始迁移之前,请使用 SharedPreferences.OnSharedPreferenceChangeListener 或静态代码分析工具(如 Android Lint / Detekt)扫描所有 getSharedPreferences、edit() 调用及其键名。建议统一抽取到 PreferenceKeys.kt 文件中,再逐条迁移至 DataStore。

4.7 本章小结

  • 所有读写代码片段已归类,从基本类型到 JSON 对象均有示例。
  • 键类型梳理表可作为迁移映射清单直接使用。
  • 现有代码存在类型不安全、键名硬编码、同步风险三大问题。
  • DataStore 通过 Flow 和 suspend 函数提供类型安全、响应式的读写方案。