关于 datastore
面向 Android 和 Kotlin Multiplatform 开发者的 Jetpack DataStore 技能,帮助选择并实现响应式存储。它比较 Preferences、类型化存储与 Room,并说明错误恢复和平台文件路径方面的注意事项。
主要功能
- 01Preferences 与类型化存储指南
- 02DataStore 与 Room 选型
- 03针对 IOException 的 Flow 处理
- 04类型化存储损坏恢复
- 05平台专属路径工厂
适用场景
- 01为设置和结构化数据选择存储
- 02安全处理 DataStore 读取错误
- 03恢复损坏的类型化存储文件
- 04在 Android、iOS 和 JVM 创建存储
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rcosteira79/ android-skills --skill datastore安装后在 Claude Code 中输入/datastore确认已加载。
详细介绍
Android 和 KMP 的 Jetpack DataStore
基于响应式协程的键值/类型化存储;同一个 androidx.datastore:datastore-preferences-core 可在 Android、iOS 和 JVM 上运行——只有文件路径创建器因平台而异。(Web 支持仅适用于 1.3.0-alpha,并且使用 sessionStorage/OPFS,而非基于文件路径。)本参考介绍存储方案的选择,以及两个错误/路径陷阱,不涵盖 Preferences 键、edit { } 或 Serializer<T> 的基础知识。改编自 Meet-Miyani/compose-skill;MIT。相关: android-skills:android-data-layer、android-skills:kmp-boundaries、android-skills:kotlin-flows。
Preferences、Typed 还是 Room
| 需求 | 存储 |
|---|---|
| 键值标记(主题、区域设置、是否完成引导) | Preferences DataStore |
| 单个类型化对象、多个相关字段、架构演进 | Typed DataStore + Serializer<T> |
关系型数据、索引、WHERE/JOIN、超过 100 条记录、分页 | Room |
| 单次写入的数据超过约 50KB | Room/文件系统——DataStore 每次 edit 都会重写整个文件 |
经验法则:如果 WHERE 子句会有用,就使用 Room。
两个陷阱
.catch 必须专门匹配 IOException——绝不能使用宽泛的 catch。 裸写 catch { emit(emptyPreferences()) } 会吞掉 CancellationException(破坏结构化并发),还会将序列化器/损坏错误隐藏在静默的空状态之后。
val settings: Flow<UserSettings> = dataStore.data
.catch { e -> if (e is IOException) emit(emptyPreferences()) else throw e }
.map { p -> UserSettings(p[Keys.DARK_MODE] ?: false, p[Keys.LOCALE] ?: "en") }
Typed DataStore 的损坏恢复由 CorruptionException 触发,而非 IOException。 序列化器的 readFrom 必须将解析失败包装为 CorruptionException,并且存储需要 ReplaceFileCorruptionHandler——没有它,一个损坏的文件就会导致每次读取都永久失败。
object AppSettingsSerializer : Serializer<AppSettings> {
override val defaultValue = AppSettings()
override suspend fun readFrom(input: InputStream): AppSettings =
try { Json.decodeFromString(input.readBytes().decodeToString()) }
catch (e: SerializationException) { throw CorruptionException("Cannot read AppSettings", e) }
override suspend fun writeTo(t: AppSettings, output: OutputStream) =
output.write(Json.encodeToString(t).encodeToByteArray())
}
val store = DataStoreFactory.create(
serializer = AppSettingsSerializer,
corruptionHandler = ReplaceFileCorruptionHandler { AppSettings() },
produceFile = { File(context.filesDir, "app_settings.json") },
)
KMP 工厂——使用各平台的路径(而非临时目录)
// commonMain
fun createPreferencesDataStore(producePath: () -> String): DataStore<Preferences> =
PreferenceDataStoreFactory.createWithPath(produceFile = { producePath().toPath() })
// android: context.filesDir.resolve(PREFS_FILE).absolutePath
// ios: NSDocumentDirectory via NSFileManager
// jvm: File(System.getProperty("user.home"), ".myapp") — NOT java.io.tmpdir (the OS may wipe it on reboot)
(在可组合项中,绝不要对 DataStore 使用 runBlocking——它会在磁盘 I/O 期间阻塞主线程,可能导致 ANR,而且每次重组都会重新运行。请暴露一个 StateFlow,并使用 collectAsStateWithLifecycle 收集。)