简介 #
鉴于我所知道的安卓本地快捷存储方案如 SharedPreferences、MMKV、DataStore 都有明显的缺点,未能兼顾好安全、
性能、类型支持、和用法简易方便的程度,个人基于 DataStore
做了一个新的方案 KDataStore。
主要有以下优化:
- 单例模式
- 通过委托生成
key。 - 采用
MutbaleStateFlow即时观察、同步读写、异步写入磁盘。 - 备份数据以处理异常。
- 优化了类型支持, 支持
Kotlin Serializable, 自定义,Nullable
详见 GitHub 仓库(含demo)。
这份中文版本和 Java 支持主要用作初期宣传,在其他作品中并没有考虑。在支持IOS之后会移到Multiplatform分组中。
竞品对比 #
| SharedPreferences | MMKV | DataStore | KDataStore | |
|---|---|---|---|---|
| 性能 | 启动: 2.5ms 读取:耗时可忽略 commit写入: 堵塞2.3msapply写入: 耗时可忽略,但不知道是否成功异步写入磁盘。 | 启动: 2.3ms 读写:耗时可忽略 | 都通过异步,故只测响应: 8.6ms | 启动: 13.5ms 文件显著增加时影响不大,亦可先行在 Application中异步启动来解决。读写:耗时可忽略 |
| 类型安全 | 否 | 否 | 是 | 是 |
| 除常见基本类型, String, Set<String> 外的类型支持 | Parcelable | 自定义 但需放在独立的 DataStore 中 | Kt Serializable (包括常见存储类型) 自定义 | |
| 读取异常 | 返回空的 HashMap, 即全部采用默认值 | 自行 catch 处理 | 启用备份文件 | |
| 写入中遇 IOException | 用未写入该数据的备份文件替换,且不再写入该数据。 如用 commit, 可通过返回的 false 获悉 | 仅 catch 不处理 | 记录,下次启动时从备份文件中更新 | |
| 多进程 | 自行封装 | 支持 | 处于 1.1.0-alpha 阶段 | 在 DataStore 1.1.0 发布之后 |
| 多平台 | 不支持 | 支持 | ||
| 加密 | 自行封装 | 支持 | 自行封装 | 需自选加密协议,实现 cipher |
| 额外优点 | 后台定时异步写入磁盘,ANR前一刻更新的数据不会丢失 | 体积小,jar on Android side 仅 12.6 kb | ||
| 额外缺点 | 断电或者系统崩溃后容易丢失很多数据 | 比较新 建模时只能使用 Kotlin, 调用时可以使用 Java。 | ||
以上测试结果采用 30 份 String 数据,机型魅族18s, 源码见 KDataStore.benchmark。
关于其他地方的存储方案对比分析,绝大多数都有严重错误。 官网相对准确,但也很片面。如想探究,建议自己测试并查阅源码。
基础用法 #
以切换主题的场景为例,使用 KDataStore 存储 Boolean 值代表当前主题。
建模 #
单独分出一个 Android 模块, 常见命名为 settings。
后续文档中的命名均参考 settings。如果不考虑从Java文件中调用,下图中的@JvmStatic则是不需要的。

关于 KDSFlow

安卓上的 actual KDSFlow 实现

调用 #
在其他模块中调用
- 在
Activity/BasicActivity中观察Flow/LiveData,绑定主题。 - 选中的
RadioButton会随用户点击自动变化,根据isDarkMOde.value设置起始状态即可,不用绑定。 - 在
RadioGroup监听中更新存值。

在Fragment中观察Flow时建议采用 collectOnResume。其配置工作在下文有包括。
- 在
Activity/BasicActivity中观察LiveData,绑定主题。 - 选中的
RadioButton会随用户点击自动变化,根据isDarkMode().getValue()设置起始状态即可,不用绑定。 - 在
RadioGroup监听中更新存值。

将 state 和主题绑定

RadioButton处更新存值

配置 #
配置 build.gradle/build.gradle.kts 如下, 或参考 Github 上的 demo
(其中有使用 version catalog)。
根目录 #
plugins{
...
id 'org.jetbrains.kotlin.plugin.serialization' version "$version_kt" apply false
}
plugins{
...
id ("org.jetbrains.kotlin.plugin.serialization") version "$version_kt" apply false
}
模型模块 #
plugins {
...
id 'kotlinx-serialization'
}
dependencies {
...
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1'
implementation 'io.github.shawxingkwok:kt-util:1.0.2'
implementation 'io.github.shawxingkwok:kdatastore:1.0.0'
}
plugins {
...
id ("kotlinx-serialization")
}
dependencies {
...
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation ("io.github.shawxingkwok:kt-util:1.0.2")
implementation ("io.github.shawxingkwok:kdatastore:1.0.0")
}
调用方 #
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach{
kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
}
dependencies {
...
implementation 'io.github.shawxingkwok:android-util-view:1.0.8'
implementation 'io.github.shawxingkwok:kdatastore:1.0.0'
implementation project(':本地模型模块名称') // 或远程仓库
}
dependencies{
...
implementation 'io.github.shawxingkwok:kdatastore:1.0.0'
implementation project(':本地模型模块名称') // 或远程仓库
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach{
kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
}
dependencies {
...
implementation ("shawxingkwok:android-util-view:1.0.8")
implementation ("io.github.shawxingkwok:kdatastore:1.0.0")
implementation (project(":本地模型模块名称")) // 或远程仓库
}
dependencies{
...
implementation ("io.github.shawxingkwok:kdatastore:1.0.0")
implementation (project(":本地模型模块名称")) // 或远程仓库
}
如果该调用模块使用了 startup-runtime, 要注意在dependencies中包含KDataStoreInitializer::class.java。
类型支持 #
kotlinx.serialization
用法类似 Java Serializable, 但多平台,且速度快两倍多。
被 Serializable 标记的 class, 基本类型,enum, Pair, IntArray, List 的默认实现等等均可视为 Serializable。

Non-null时需声明默认值。Nullable时默认值被限制为null。- 自定义时需实现与
Kt Serializable之间的相互转换。(convert/recover)
在对象的内部修改并不会触发磁盘更新。
迁移 #
类比下图格式(判断存在 -> 迁移 -> 删除)从其他存储仓库迁移过来。其中的 appContext 源自 KDataStore.
比如取自 SharedPreferences

此外内置 delete, exist 两个函数辅助从 KDataStore 迁移到别处。


可选参数 #


Java 标准库或其他库中自选加密协议,实现 cipher。加密会将启动时间提升一倍左右。Android 在 api 29 版本引入了沙盒机制,实现了数据隔离,脱离加密也相对安全。
重置 #
全部 #


局部 #
比如重置声明过的 isDarkMode
Settings.isDarkMode.reset()
Settings.isDarkMode().reset();
