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.OnSharedPreferenceChangeListener 或静态代码分析工具(如 Android Lint / Detekt)扫描所有 getSharedPreferences、edit() 调用及其键名。建议统一抽取到 PreferenceKeys.kt 文件中,再逐条迁移至 DataStore。
4.7 本章小结
- 所有读写代码片段已归类,从基本类型到 JSON 对象均有示例。
- 键类型梳理表可作为迁移映射清单直接使用。
- 现有代码存在类型不安全、键名硬编码、同步风险三大问题。
- DataStore 通过 Flow 和 suspend 函数提供类型安全、响应式的读写方案。