找回密码
 立即注册
查看: 300|回复: 0

使用 Jetpack DataStore 进行数据存储

[复制链接]
发表于 2022-1-13 15:38 | 显示全部楼层 |阅读模式
欢迎使用 Jetpack DataStore,这是一个经过改进的全新数据存储解决方案,旨在替代原有的 SharedPreferences。Jetpack DataStore 基于 Kotlin 协程和 Flow 开发,并提供两种不同的实现: Proto DataStorePreferences DataStore。其中 Proto DataStore,可以存储带有类型的对象 (使用 protocol buffers 实现);Preferences DataStore,可以存储键值对。在 DataStore 中,数据以异步的、一致的、事务性的方式进行存储,克服了 SharedPreferences 的大部分缺点。

    protocol buffers

    https://developers.google.cn/protocol-buffers

SharedPreferences 和 DataStore 对比

图片


    SharedPreferences 有一个看上去可以在 UI 线程安全调用的同步 API,但是该 API 实际上执行了磁盘 I/O 操作。此外,apply() 方法会在 fsync() 阻塞 UI 线程。在您应用的任何地方,每当 Service 或 Activity 启动或停止时,就会触发等待 fsync() 的调用。由 apply() 安排的 fsync() 调用过程会阻塞 UI 线程,这也常常成为造成 ANR 的源头。** SharedPreferences 在分析出错时会抛出运行时异常。

    ANRhttps://developer.android.google.cn/topic/performance/vitals/anr

在两种实现中,除非另外特指,否则 DataStore 会将首选项存储在文件中,并且所有的数据操作都会在 Dispatchers.IO 上执行。

虽然 Preferences DataStore 与 Proto DataStore 都可以存储数据,但它们的实现方法不尽相同:

    Preference DataStore,就像 SharedPreferences 一样,不能定义 schema 或保证以正确的类型访问键值。

    Proto DataStore 让您可以使用 Protocol buffers 定义 schema。使用 Protobufs 可以保留强类型数据。它们相对于 XML 或其他相似的数据格式要更快、更小、歧义更少。虽然 Proto DataStore 要求您学习一种新的序列化机制,但考虑到 Proto DataStore 所带来的强类型 schema 的优势,我们认为这样的代价是值得的。

    Protocol buffershttps://developers.google.cn/protocol-buffers

Room 和 DataStore 对比

如果您有局部更新数据、参照完整性或支持大型、复杂数据集的需求,则应当考虑使用 Room 而不是 DataStore。DataStore 是小型、简单数据集的理想选择,它并不支持局部更新与参照完整性。

使用 DataStore

首先添加 DataStore 依赖项。如果您使用的是 Proto DataStore,请确保您也添加了 proto 依赖项:

    proto 依赖项

    https://github.com/google/protobuf-gradle-plugin
def dataStoreVersion = "1.0.0-alpha05" // 在 Android 开发者网站上确认最新的版本号 // https://developer.android.google.cn/jetpack/androidx/releases/datastore// Preferences DataStoreimplementation "androidx.datastore:datastore-preferences:$dataStoreVersion"// Proto DataStoreimplementation  "androidx.datastore:datastore-core:$dataStoreVersion"
当您使用 Proto DataStore 时,您需要在 app/src/main/proto/ 目录下使用 proto 文件定义您自己的 schema。有关定义 proto schema 的更多信息,请参阅 protobuf 语言指南。
    protobuf 语言指南
    https://developers.google.cn/protocol-buffers/docs/proto3
syntax = "proto3";option java_package = "<your package name here>";option java_multiple_files = true;message Settings {  int my_counter = 1;}
创建 DataStore

您可以使用 Context.createDataStore() 扩展方法创建 DataStore:
// 创建 Preferences DataStore val dataStore: DataStore<Preferences> = context.createDataStore(    name = "settings")
如果您使用的是 Proto DataStore,您还需要实现 Serializer 接口来告诉 DataStore 如何读取和写入您的数据类型。
object SettingsSerializer : Serializer<Settings> {    override fun readFrom(input: InputStream): Settings {        try {            return Settings.parseFrom(input)        } catch (exception: InvalidProtocolBufferException) {            throw CorruptionException("Cannot read proto.", exception)        }    }    override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)}// 创建 Proto DataStoreval settingsDataStore: DataStore<Settings> = context.createDataStore(    fileName = "settings.pb",    serializer = SettingsSerializer)
从 DataStore 读取数据

无论是 Preferences 对象还是您在 proto schema 中定义的对象,DataStore 都会以 Flow 的形式暴露已存储的数据。DataStore 可以确保在 Dispatchers.IO 上检索数据,因此不会阻塞您的 UI 线程。

    Dispatchers.IO

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html

使用 Preferences DataStore:
val MY_COUNTER = preferencesKey<Int>("my_counter")val myCounterFlow: Flow<Int> = dataStore.data     .map { currentPreferences ->        // 不同于 Proto DataStore,这里不保证类型安全。        currentPreferences[MY_COUNTER] ?: 0      }
使用 Proto DataStore:
val myCounterFlow: Flow<Int> = settingsDataStore.data    .map { settings ->        // myCounter 属性由您的 proto schema 生成!        settings.myCounter     }
向 DataStore 写入数据

为了写入数据,DataStore 提供了一个 DataStore.updateData() 挂起函数,它会将当前存储数据的状态作为参数提供给您,对于 Preferences 对象或是您在 proto schema 中定义的对象实例皆为如此。updateData() 函数使用原子的读、写、修改操作并以事务的方式更新数据。当数据在磁盘上完成存储时,此协程就会完成。

Preferences DataStore 还提供了一个 DataStore.edit() 函数来方便数据的更新。在此函数中,您会收到一个用于编辑的 MutablePreferences 对象,而不是 Preferences 对象。该函数与 updateData() 一样,会在转换代码块完成之后将修改应用到磁盘,并且当数据在磁盘上完成存储时,此协程就会完成。

使用 Preferences DataStore:
suspend fun incrementCounter() {    dataStore.edit { settings ->        // 可以安全地增加我们的计数器,而不会因为资源竞争而丢失数据。        val currentCounterValue = settings[MY_COUNTER] ?: 0        settings[MY_COUNTER] = currentCounterValue + 1    }}
使用 Proto DataStore:
suspend fun incrementCounter() {    settingsDataStore.updateData { currentSettings ->        // 可以安全地增加我们的计数器,而不会因为资源竞争而丢失数据。        currentSettings.toBuilder()            .setMyCounter(currentSettings.myCounter + 1)            .build()    }}
**从 SharedPreferences 迁移至 DataStore **

要从 SharedPreferences 迁移至 DataStore,您需要将 SharedPreferencesMigration 对象传递给 DataStore 构造器,DataStore 可以自动完成从 SharedPreferences 迁移至 DataStore 的工作。迁移会在 DataStore 中发生任何数据访问之前运行,这意味着在 DataStore.data 返回任何值以及 DataStore.updateData() 可以更新数据之前,您的迁移必须已经成功。

如果您要迁移至 Preferences DataStore,您可以使用 SharedPreferencesMigration 的默认实现。只需要传入 SharedPreferences 构造时所使用的名字就可以了。

使用 Preferences DataStore:
val dataStore: DataStore<Preferences> = context.createDataStore(    name = "settings",    migrations = listOf(SharedPreferencesMigration(context, "settings_preferences")))
当需要迁移至 Proto DataStore 时,您必须实现一个映射函数,用来定义如何将 SharedPreferences 所使用的键值对迁移到您所定义的 DataStore schema。

使用 Proto DataStore:
val settingsDataStore: DataStore<Settings> = context.createDataStore(    produceFile = { File(context.filesDir, "settings.preferences_pb") },    serializer = SettingsSerializer,    migrations = listOf(        SharedPreferencesMigration(            context,            "settings_preferences"                    ) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->            // 在这里将 sharedPrefs 映射至您的类型。          }    ))
总结

SharedPreferences 有着许多缺陷: 看起来可以在 UI 线程安全调用的同步 API 其实并不安全、没有提示错误的机制、缺少事务 API 等等。DataStore 是 SharedPreferences 的替代方案,它解决了 Shared Preferences 的绝大部分问题。DataStore 包含使用 Kotlin 协程和 Flow 实现的完全异步 API,可以处理数据迁移、保证数据一致性,并且可以处理数据损坏。

Android高级开发系统进阶笔记、最新面试复习笔记PDF,我的GitHub


文末


您的点赞收藏就是对我最大的鼓励!
欢迎关注我,分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-5-14 13:10 , Processed in 0.137546 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表