中文

简介 #

鉴于我所知道的安卓本地快捷存储方案如 SharedPreferencesMMKVDataStore 都有明显的缺点,未能兼顾好安全、 性能、类型支持、和用法简易方便的程度,个人基于 DataStore 做了一个新的方案 KDataStore

主要有以下优化:

  • 单例模式
  • 通过委托生成 key
  • 采用 MutbaleStateFlow 即时观察、同步读写、异步写入磁盘。
  • 备份数据以处理异常。
  • 优化了类型支持, 支持 Kotlin Serializable, 自定义, Nullable

详见 GitHub 仓库(含demo)。

这份中文版本和 Java 支持主要用作初期宣传,在其他作品中并没有考虑。
在支持 IOS 之后会移到 Multiplatform 分组中。

竞品对比 #

SharedPreferencesMMKVDataStoreKDataStore
性能启动: 2.5ms

读取:耗时可忽略

commit写入: 堵塞2.3ms

apply写入: 耗时可忽略,但不知道是否成功异步写入磁盘。
启动: 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 实现

调用 #

在其他模块中调用

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