简介 #
鉴于我所知道的安卓本地快捷存储方案如 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();