DataStore:类型安全偏好存储

发布时间:2026/7/22 8:32:53

DataStore:类型安全偏好存储 文章目录第 1 章 SharedPreferences 的局限为什么换 DataStore原理补充踩坑动手练第 2 章 Preferences DataStore键值与 Flow踩坑动手练第 3 章 Proto DataStore强类型与 schema原理补充踩坑动手练第 4 章 ViewModel 收集与 UI 绑定踩坑动手练第 5 章 从 SharedPreferences 迁移踩坑动手练第 6 章 错误处理、测试与反模式动手练面试速查 · 追问链追问链 #1DataStore 和 SharedPreferences 核心差异 追问链 #2Preferences 和 Proto DataStore 怎么选 追问链 #3为什么必须单例 DataStore ⭐追问链 #4迁移要注意什么 ⭐追问链 #5DataStore 适合存 token 吗 完整链路一句通相关推荐第 1 章 SharedPreferences 的局限为什么换 DataStoreSharedPreferences在 Java 工程里长期承担「存个开关、记个 token」的工作但生产痛点明显无类型安全getString键名拼错返回null编译期不报错。主线程磁盘 IOcommit/apply可能阻塞 UIapply仍可能触发同步写。无结构化观察OnSharedPreferenceChangeListener与协程 Flow 生态割裂。进程被杀一致性极端情况下apply异步丢失。新功能写入默认 DataStorePreferences 或 Proto。SharedPreferences 仅作遗留维护与迁移来源。// 遗留对照 — 不推荐新写入// prefs.edit().putBoolean(dark_mode, true).apply()DataStore 提供异步 IO、Flow 观察、事务式更新edit { }块原子、类型化 APIProto。原理补充DataStore 基于 Kotlin 协程单文件单实例by preferencesDataStore委托保证。读写串行化避免 SP 的多进程竞态仍非跨进程分布式存储。踩坑同一文件名创建多个 DataStore 实例 → 未定义行为用单例委托。在Activity里到处context.dataStore.edit→ 应收敛到 Repository。把大 JSON 配置全塞 Preferences → 考虑 Proto 或 Room。动手练A列举 SharedPreferences 两个 DataStore 已解决的问题。D搜索工程SharedPreferences新写入点标记可迁移项。第 2 章 Preferences DataStore键值与 Flow最接近 SP 的替代键用preferencesKey声明privatevalContext.userPrefs:DataStorePreferencesbypreferencesDataStore(nameuser_prefs,)objectPrefKeys{valDARK_MODEbooleanPreferencesKey(dark_mode)valLAST_USER_IDstringPreferencesKey(last_user_id)}classUserPreferencesRepository(privatevaldataStore:DataStorePreferences,){valdarkModeFlow:FlowBooleandataStore.data.map{prefs-prefs[PrefKeys.DARK_MODE]?:false}.distinctUntilChanged()suspendfunsetDarkMode(enabled:Boolean){dataStore.edit{prefs-prefs[PrefKeys.DARK_MODE]enabled}}}dataStore.data是FlowPreferences键变更时重新发射整个快照用mapdistinctUntilChanged收窄到关心的键。踩坑键名字符串散落各处 → 集中到PrefKeysobject。忘记distinctUntilChanged→ UI 重复渲染。在map里做重计算 → 改用map后flowOn(Default)。动手练B起点补全lastUserIdFlow与setLastUserId≤10 行。第 3 章 Proto DataStore强类型与 schema配置结构复杂、字段多、要演进时用Proto DataStore用 protobuf 定义 schema// user_settings.proto syntax proto3; option java_package com.example.datastore; option java_multiple_files true; message UserSettings { bool dark_mode 1; string locale 2; int32 font_scale 3; }privatevalContext.settingsStore:DataStoreUserSettingsbydataStore(fileNameuser_settings.pb,serializerUserSettingsSerializer,)objectUserSettingsSerializer:SerializerUserSettings{overridevaldefaultValue:UserSettingsUserSettings.getDefaultInstance()overridesuspendfunreadFrom(input:InputStream):UserSettingsUserSettings.parseFrom(input)overridesuspendfunwriteTo(t:UserSettings,output:OutputStream){t.writeTo(output)}}更新suspendfunsetLocale(locale:String){settingsStore.updateData{current-current.toBuilder().setLocale(locale).build()}}updateData提供读-改-写事务失败时自动回滚到上一致快照。原理补充Proto 保证字段编号稳定新增字段用新 tag旧客户端忽略未知字段。比 JSON 字符串更适合作版本演进。踩坑改 field number → 破坏兼容只能加新字段。Serializer抛异常未处理 → 触发 corruption 流程第 6 章。Proto 与 Preferences 混用同一文件名 → 禁止。动手练A何时选 Proto 而非 Preferences给两个判断标准。第 4 章 ViewModel 收集与 UI 绑定与 Room 类似DataStore 的Flow在 ViewModel 转为StateFlowclassSettingsViewModel(privatevalprefs:UserPreferencesRepository,):ViewModel(){valuiState:StateFlowSettingsUiStateprefs.darkModeFlow.map{dark-SettingsUiState(darkModedark)}.stateIn(scopeviewModelScope,startedSharingStarted.WhileSubscribed(5_000),initialValueSettingsUiState(),)funonDarkModeChanged(enabled:Boolean){viewModelScope.launch{prefs.setDarkMode(enabled)}}}ComposecollectAsStateWithLifecycle()。ViewrepeatOnLifecycle(STARTED)。不要在 Composable 里直接context.dataStore——难以测试且生命周期混乱。踩坑stateIn的initialValue与首帧真实值不一致导致闪烁 → 用合理默认或WhileSubscribed(0)视场景调整。在init同步读 DataStore → 用 Flow 或runBlocking仅测试生产用 suspend。多处edit竞态 → DataStore 已串行仍应避免业务层并发写同一键无合并策略。动手练C把darkModeFlow与lastUserIdFlowcombine成SettingsUiState骨架 8 行。第 5 章 从 SharedPreferences 迁移官方提供SharedPreferencesMigration在首次打开 DataStore 时导入旧键privatevalContext.userPrefs:DataStorePreferencesbypreferencesDataStore(nameuser_prefs,produceMigrations{context-listOf(SharedPreferencesMigration(contextcontext,sharedPreferencesNamelegacy_user_prefs,keysToMigratesetOf(dark_mode,last_user_id),),)},)迁移后停止向旧 SP 写入读也逐步切到 DataStore。多进程 SP 需评估是否曾用MODE_MULTI_PROCESS已废弃。Hilt 提供 DataStoreModuleInstallIn(SingletonComponent::class)objectDataStoreModule{ProvidesSingletonfunprovideUserPrefs(ApplicationContextcontext:Context):DataStorePreferencescontext.userPrefs}踩坑迁移键漏列 → 用户设置丢一半。迁移跑多次逻辑依赖 SP 仍存在 → 设计一次性迁移或版本标记。新旧双写一段时间 → 以 DataStore 为准SP 只读不写。动手练D列出所在 App 可迁移的 SP 文件名与键表格 3 行即可。第 6 章 错误处理、测试与反模式CorruptionHandler文件损坏时恢复默认或重建bypreferencesDataStore(nameuser_prefs,corruptionHandlerReplaceFileCorruptionHandler(produceNewData{emptyPreferences()},),)单元测试用内存 DataStorefuncreateTestDataStore():DataStorePreferencesPreferenceDataStoreFactory.create(scopeTestScope(),produceFile{File.createTempFile(test_prefs,.preferences_pb)},)反模式改法新功能getSharedPreferencesDataStoreActivity 直接 editRepository存 5MB 配置串Proto 拆字段或 Room无distinctUntilChanged收窄 Flow多 DataStore 同名单例委托Token 等敏感数据DataStore 不加密磁盘高敏感用EncryptedFile/ Android Keystore 或服务器会话策略别误以为「换了 DataStore 就安全」。动手练AReplaceFileCorruptionHandler何时触发B起点修复在 UI 线程runBlocking { dataStore.edit { } }的写法。面试速查 · 追问链追问链 #1DataStore 和 SharedPreferences 核心差异 参考回答DataStore 异步 IO、协程edit/updateData事务、Kotlin Flow 观察类型键或 Proto schema。SP 同步风险、无 Flow、键字符串易错。新写入用 DataStoreSP 仅遗留与迁移。追问 1能否多进程答都不是跨进程首选多进程需专门方案别假设 SP/DataStore 自动同步。追问 2apply和 DataStore 写谁更安全答DataStore 串行协程写一致性更好仍非加密存储。追问链 #2Preferences 和 Proto DataStore 怎么选 参考回答少量布尔/字符串用 Preferences preferencesKey。结构多、要版本演进用 Proto updateData。Proto 编译期 schemaPreferences 更轻。大对象两者都不合适用 Room。追问 1Proto 字段能删吗答可标记reserved不要复用 field number。追问链 #3为什么必须单例 DataStore ⭐参考回答同文件名多实例会导致读写冲突和未定义行为。by preferencesDataStore委托在Context扩展上保证单例。DI 模块Singleton提供同一实例。追问 1dataStore.data冷还是热答冷 Flow收集时开始读盘并监听变更。追问链 #4迁移要注意什么 ⭐参考回答SharedPreferencesMigration指定旧名与键首次 DataStore 读时执行。迁移后停写 SP。测升级路径旧版 SP 安装 → 升新版 → 值仍在。追问 1键名改了怎么办答迁移映射里读旧键写新preferencesKey或自定义DataMigration。追问链 #5DataStore 适合存 token 吗 参考回答磁盘明文不替代加密存储。低敏感偏好主题、引导页已读合适。高敏感 token 用 EncryptedSharedPreferences遗留或 Keystore 加密方案并配合服务端刷新策略。完整链路一句通PrefKeys 集中声明→Repository 封装edit/updateData→Flow distinctUntilChanged→ViewModelstateIn→SP 一次性 Migration→CorruptionHandler 兜底。相关推荐Kotlin 语法与空安全Android 开发第一课Kotlin 作用域函数let/apply 工程选型

相关新闻