📜  Android 中的 Jetpack 数据存储

📅  最后修改于: 2022-05-13 01:58:44.125000             🧑  作者: Mango

Android 中的 Jetpack 数据存储

Jetpack DataStore 是一种数据存储解决方案,它使用协议缓冲区来存储键值对或类型化对象。 DataStore 使用 Kotlin 协程和 Flow 以异步、可靠和事务方式存储数据。在本文中,我们将了解为什么需要 Jetpack DataStore Preferences,如何在我们的 Android 应用程序中构建它们,以及如何将 SharedPreferences 移动到 DataStore Preferences。

博客中将讨论以下主题:

  1. 为什么要使用 Jetpack DataStore?
  2. 数据存储首选项与 SharedPreferences
  3. Jetpack 数据存储首选项实现
  4. 将 SharedPreferences 传输到 DataStore Preferences

但是,首先也是最重要的:

1. 为什么会有新的 Jetpack DataStore?

根据官方文档:

  • Jetpack DataStore 是一种持久且增强的数据存储,旨在彻底改变我们存储首选项的方式。
  • 它基于 Kotlin 中的协程和流程。
  • 数据以异步、一致和事务方式保存,消除了 SharedPreferences 的大部分缺点。

上述增强功能非常棒。

示例:假设您正在开发一个下载量很大的 Android 应用程序,该程序在 Google Play 上可用,并且随着时间的推移它经历了多次升级和错误补丁。结果,应用程序在某个时候开始出现 ANR(应用程序无响应)。 ANR 的原因是程序正在 UI 线程上执行长时间运行的操作。 (超过 5 秒)

2. 这个 ANR 的目的是什么?

主要问题是我们的共享首选项文件变得太大,因为我们继续一一添加新的键值对。在 UI 线程上,我们试图在程序启动后立即检索特定键的值。但是,当您第一次访问 SharedPreferences 时,它会读取整个文件并将内容加载到内存中。这发生在我们的 UI 线程上。

这是一个输入/输出操作。这可能需要一些时间。在我们的示例中,它导致较大文件的 ANR。此外,Jetpack DataStore 的当前实现不鼓励在 UI 线程上读取数据。这是我最喜欢的方面之一。现在,我们正在将 Jetpack DataStore 集成到我们的应用程序中。让我们比较一下 SharedPreferences 和 DataStore Preferences

DataStore Preferences

SharedPreferences

Has async APICan make async API calls
This is not encouraged by DataStore Preferences.SharedPreferences give a simple Synchronous API, however, it is not safe to use on the UI thread.
DataStore Preferences ensure consistency.This in contrast does not ensure that consistency
DataStore Preferences allow error handlingNo error handling in SharedPrefs
DataStore Preferences by default support the Kotlin Coroutines Flow API.The shared prefs need additional configuration for making the coroutines flow.

3. Jetpack DataStore Preferences 实现

在应用的 build.gradle 文件中包含以下依赖项。

implementation "androidx.datastore:datastore-preferences:1.0.0"

现在,与 SharedPreferences 对象类似,我们必须构建 DataStore Preferences 对象。

Kotlin
val gfgDataStore: DataStore =
    context.createDataStore(name = "gfg-datastore")
    // A data store object sample


Kotlin
fun  DataStore.getValueFlow(
    your_key: Preferences.Key,
    someDefaultValue: gfg
    // Value is gfg here
): Flow {
    return this.data
        .catch { exception ->
            if (exception is IOException) {
                emit(emptyPreferences())
            } else {
                // Exception handling
                throw exception
            }
        }.map { preferences ->
            preferences[your_key] ?: someDefaultValue
        }
}
  
suspend fun  DataStore.setValue(your_key: Preferences.Key, value: gfg) {
    this.edit { preferences ->
        preferences[your_key] = value
    }
}


Kotlin
companion object {
    private val GEEKSFORGEEKS = preferencesKey("spandan-saxena")
}


Kotlin
sampleViewModel.launch {
    dataStore.getValueFlow(GEEKSFORGEEKS, "")
        .collect { value ->
            // get the value and then use it
        }
}


Kotlin
sampleViewModel.launch {
    dataStore.getValueFlow(GEEKSFORGEEKS, "")
        .catch {
            // exception handling
        }
        .collect { value ->
            // collect the value
        }
}


Kotlin
val sampleDataStore: DataStore =
    context.createDataStore(
        name = "gfg-prefs",
        migrations = listOf(SharedPreferencesMigration(context, "gfg-prefs"))
    )


然后我们构造两个扩展函数来读写数据。这只是为了您的方便。

科特林

fun  DataStore.getValueFlow(
    your_key: Preferences.Key,
    someDefaultValue: gfg
    // Value is gfg here
): Flow {
    return this.data
        .catch { exception ->
            if (exception is IOException) {
                emit(emptyPreferences())
            } else {
                // Exception handling
                throw exception
            }
        }.map { preferences ->
            preferences[your_key] ?: someDefaultValue
        }
}
  
suspend fun  DataStore.setValue(your_key: Preferences.Key, value: gfg) {
    this.edit { preferences ->
        preferences[your_key] = value
    }
}

然后,如下所示,我们建立 Preferences Keys:

科特林

companion object {
    private val GEEKSFORGEEKS = preferencesKey("spandan-saxena")
}

现在,这就是我们从 DataStore Preferences 中读取以下数据的方式:

科特林

sampleViewModel.launch {
    dataStore.getValueFlow(GEEKSFORGEEKS, "")
        .collect { value ->
            // get the value and then use it
        }
}

在这种情况下,我们可以通过在流上使用 catch运算符来管理问题。

科特林

sampleViewModel.launch {
    dataStore.getValueFlow(GEEKSFORGEEKS, "")
        .catch {
            // exception handling
        }
        .collect { value ->
            // collect the value
        }
}

这就是我们可以轻松地将其合并到我们的 Android 应用程序中的方式。现在让我们讨论从 SharedPreferences 到 DataStore Preferences 的转换。

4. 将 SharedPreferences 转移到 DataStore Preferences

在迁移方面,DataStore 会处理一切。我们只需要提供 SharedPreferences 的名称。例如,如果 SharedPreferences 的名称是“gfg-prefs”,我们必须执行以下操作:

科特林

val sampleDataStore: DataStore =
    context.createDataStore(
        name = "gfg-prefs",
        migrations = listOf(SharedPreferencesMigration(context, "gfg-prefs"))
    )

结论

我们可以看到还有其他可用的替代方案,我们可以根据我们的用例采用这些替代方案。通过这种方式,从 SharedPreferences 迁移到 DataStore Preferences 变得更加容易。 SharedPreferences 有许多缺点,包括在 UI 线程上调用看起来安全的异步 API、没有报告失败的方法、没有事务性 API 等等。 DataStore 是 SharedPreferences 的替代品,可解决大多数此类问题。 DataStore 提供了一个使用 Kotlin 协程和 Flow 构建的完全异步 API,用于管理数据迁移、数据一致性和数据损坏。