Jetpack架构组件库:DataStore

DataStore 简介

Jetpack DataStore 是一种经过改进的新数据存储解决方案,旨在取代 SharedPreferences。DataStore 基于 Kotlin 协程和 Flow 构建而成,提供以下两种不同的实现:

  • Preferences DataStore 用于键值对存储。数据以异步、一致和事务性的方式存储,有助于避免 SharedPreferences 的一些缺点。此实现不需要预定义的架构,也不确保类型安全。
  • Proto DataStore 用于存储类型化对象,数据作为自定义数据类型的实例进行存储。此实现要求您使用协议缓冲区来定义架构,但可以确保类型安全。 与 XML 和其他类似的数据格式相比,协议缓冲区速度更快、规格更小、使用更简单,并且更清楚明了。
功能 SharedPreferences PreferencesDataStore ProtoDataStore
异步 API ✅(仅用于通过监听器读取已更改的值) ✅(通过 Flow 以及 RxJava 2 和 3 Flowable) ✅(通过 Flow 以及 RxJava 2 和 3 Flowable)
同步 API ✅(但无法在界面线程上安全调用)
可在界面线程上安全调用 ✅(这项工作已在后台移至 Dispatchers.IO) ✅(这项工作已在后台移至 Dispatchers.IO)
可以提示错误
不受运行时异常影响
包含一个具有强一致性保证的事务性 API
处理数据迁移
类型安全 ✅ 使用协议缓冲区

SharedPreferences的缺陷:

  • SharedPreferences 有一个看上去可以在界面线程中安全调用的同步 API,但是该 API 实际上执行磁盘 I/O 操作。此外,apply() 会阻断 fsync() 上的界面线程。每次有服务启动或停止以及每次 activity 在应用中的任何地方启动或停止时,系统都会触发待处理的 fsync() 调用。界面线程在 apply() 调度的待处理 fsync() 调用上会被阻断,这通常会导致 ANR

  • SharedPreferences 还会将解析错误作为运行时异常抛出。

如果您当前在使用 SharedPreferences 存储数据,请考虑迁移到 DataStore

注意:如果您需要支持大型或复杂数据集、部分更新或参照完整性,请考虑使用 Room,而不是 DataStore。DataStore 的目的是存储简单的小型数据集, 但不支持部分更新或引用完整性。

为了正确使用 DataStore,请始终谨记以下规则:

  1. 请勿在同一进程中为给定文件创建多个 DataStore 实例,否则会破坏所有 DataStore
    功能。如果给定文件在同一进程中有多个有效的 DataStoreDataStore 在读取或更新数据时将抛出
    IllegalStateException

  2. DataStore 的通用类型必须不可变。更改 DataStore 中使用的类型会导致 DataStore 提供的所有保证失效,并且可能会造成严重的、难以发现的 bug。强烈建议您使用可保证不可变性、具有简单的 API
    且能够高效进行序列化的协议缓冲区。

  3. 切勿在同一个文件中混用 SingleProcessDataStore 和 MultiProcessDataStore。如果您打算从多个进程访问 DataStore,请始终使用 MultiProcessDataStore

Preferences DataStore 的使用

Preference DataStore API 类似于 SharedPreferences,但与后者相比存在一些显著差异:

  • 以事务方式处理数据更新
  • 公开表示当前数据状态的 Flow
  • 不提供存留数据的方法(apply()、commit())
  • 不返回对其内部状态的可变引用
  • 通过类型化键提供类似于 Map 和 MutableMap 的 API

添加依赖:

dependencies {
   
    implementation("androidx.datastore:datastore-preferences:1.0.0") 
}

Preferences DataStore 实现使用 DataStore 和 Preferences 类将简单的键值对保留在磁盘上。

创建 Preferences DataStore

使用由 preferencesDataStore 提供的属性委托来创建 Datastore<Preferences> 实例。只需在 Kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性访问该实例。这样可以更轻松地将 DataStore 保留为单例。

private const val USER_PREFERENCES_NAME = "user_preferences"

private val Context.dataStore by preferencesDataStore(
    name = USER_PREFERENCES_NAME
)

从 Preferences DataStore 读取数据

由于 Preferences DataStore 不使用预定义的架构,因此必须使用相应的键类型函数为需要存储在 DataStore<Preferences> 实例中的每个值定义一个键。例如,如需为 int 值定义一个键,请使用 intPreferencesKey()。然后,使用 DataStore.data 属性,通过 Flow 提供适当的存储值。

private object PreferencesKeys {
   
     val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
     val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
 }

 val counterFlow: Flow<Int> = context.dataStore.data.map {
    preferences ->
         preferences[EXAMPLE_COUNTER] ?: 0
 }
	
 val completeFlow: Flow<Boolean> = context.dataStore.data.map {
    preferences ->
        preferences[SHOW_COMPLETED] ?: false
}	

如果要读取的内容很多,可以定义一个data class来存储,在dataStore.data.map中返回该数据类对象即可:

data class UserPreferences(val count: Int, val show: Boolean)

val userPreferenceFlow = context.dataStore.data.map {
    preferences ->
    val count = preferences[EXAMPLE_COUNTER] ?: 0
    val show = preferences[SHOW_COMPLETED] ?: false
    UserPreferences(count, show)
}

处理读取数据时的异常

当 DataStore 从文件读取数据时,如果读取数据期间出现错误,系统会抛出 IOExceptions。我们可以通过以下方式处理这些事务:在 map() 之前使用 catch() Flow 运算符,并且在抛出的异常是 IOException 时发出 emptyPreferences()。如果出现其他类型的异常,最好重新抛出该异常。

val userPreferenceFlow = context.dataStore.data
    .catch {
    exception ->
        // dataStore.data throws an IOException when an error is encountered when reading data
        if (exception is IOException) {
   
            emit(emptyPreferences())
        } else {
   
            throw exception
        }
    }.map {
    preferences ->
        val count = preferences[EXAMPLE_COUNTER] ?: 0
        val show = preferences[SHOW_COMPLETED] ?: false
        UserPreferences(count, show)
    }

也可以选择在外面包裹一层 try-catch 进行处理。

将数据写入 Preferences DataStore

Preferences DataStore 提供了一个 edit() 函数,用于以事务方式更新 DataStore 中的数据。该函数的 transform 参数接受代码块,您可以在其中根据需要更新值。转换块中的所有代码均被视为单个事务。

 suspend fun updateShowCompleted(showCompleted: Boolean) {
   
     try {
   
         context.dataStore.edit {
    preferences ->
             val currentCounterValue = preferences[EXAMPLE_COUNTER] ?: 0
             preferences[EXAMPLE_COUNTER] = currentCounterValue + 1
             preferences[SHOW_COMPLETED] = showCompleted
         }
     } catch (e: IOException) {
   
         println(e)
     }
 }

如果在读取或写入磁盘时发生错误,edit() 可能会抛出 IOException。如果转换块中出现任何其他错误,edit() 将抛出异常。

结合 ViewModel 和 Compose 使用的完整示例

下面是一个在 Compose 中使用包含 ViewModel 、Repository 和 DataStore 的完整示例:

// DataStore.kt
private const val USER_PREFERENCES_NAME = "user_preferences"

private val Context.dataStore by preferencesDataStore(
    name = USER_PREFERENCES_NAME
)

private object PreferencesKeys {
   
    val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
    val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
}

data class UserPreferences(val count: Int, val show: Boolean)

class UserPreferencesRepository(val context: Context) {
     

    val userPreferenceFlow = context.dataStore.data
        .catch {
    exception ->
            // dataStore.data throws an IOException when an error is encountered when reading data
            if (exception is IOException) {
   
                emit(emptyPreferences())
            } else {
   
                throw exception
            }
        }.map {
    preferences ->
            val count = preferences[EXAMPLE_COUNTER] ?: 0
            val show = preferences[SHOW_COMPLETED] ?: false
            UserPreferences(count, show)
        } 

    suspend fun updateShowCompleted(showCompleted: Boolean) {
   
        try {
   
            context.dataStore.edit {
    preferences ->
                val currentCounterValue = preferences[EXAMPLE_COUNTER] 
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Jetpack是一套基于Android开发语言Kotlin的组件,目的是帮助Android开发者快速构建健壮的、高效的应用程序。Jetpack架构组件从入门到精通是一本介绍Jetpack组件的电子书,其内容向读者提供了Jetpack组件的最基础的知识体系,以及对组件的更高阶的实践知识和技能分享。 在读者学习这本电子书时,首先会学习到基本的Jetpack概念,并了解到如何使用各种工具来实现底层代码、布局和资源文件的编写。同时,电子书重点讲解了Jetpack组件中最常用的组件,比如ViewModel、LiveData、Room、WorkManager等,它们各自的功能和用法,让读者熟知Jetpack组件的基本运作原理。读者还会学习如何将这些组件综合使用,以构造真正的应用程序。在内容的后半部分,电子书重点讲解了Jetpack架构层面的设计,以及如何更有效的使用和管理组件。 总之,Jetpack架构组件从入门到精通这本电子书非常适合想要学习Jetpack组件的Android初学者和有一定开发经验的开发者,能够帮助他们快速掌握Jetpack组件,以及提高软件的可扩展性和有效性。 ### 回答2: Jetpack架构组件是一组Android软件组件,通过这些组件,开发者可以更加轻松地开发应用程序,并提供高质量的用户体验。Jetpack架构组件包括众多不同的组件,这些组件都具有不同的用途和功能。对于开发者来说,学习Jetpack架构组件是非常重要的。 《jetpack架构组件从入门到精通 pdf》是一份针对Jetpack架构组件的学习资料。它是可以帮助开发者更好地理解和掌握Jetpack架构组件的重要手册。通过这份PDF文档,开发者可以学习到Jetpack架构组件的基本概念、用途和功能,并了解如何将这些组件应用到他们的应用程序开发中。 这份PDF文档涵盖了Jetpack架构组件的多个方面,包括ViewModel、LiveData、Data Binding、Room、Navigation、WorkManager等等。每个章节都包含了详细的介绍和具体的用例,从而帮助开发者更好地理解每个组件的用途和功能。 无论是初学者还是经验丰富的开发者,都可以从这份PDF文档中获益。它可以帮助初学者更轻松地入门Jetpack架构组件,并提供了有用的工具和技巧。对于经验丰富的开发者来说,这份PDF文档可以帮助他们更深入地了解Jetpack架构组件,并提供了一些高级技术和策略,以优化应用程序性能和用户体验。 总之,Jetpack架构组件从入门到精通的PDF文档是一份非常有价值的资源,它可以帮助开发者更好地理解和掌握Jetpack架构组件,从而更加轻松地开发高质量的Android应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值