2021-08-06 Jetpack之DataStore介绍和工具类的封装

DataStore介绍

Jetpack DataStore是一种用来替换SharedPreferences新型数据存储解决方案。
异步一致的事务方式存储数据,DataStore
保证原子性,一致性,隔离性,持久性。它是线程安全,且非阻塞的,DataStore在使用上强制开发者将其放在协程中进行调用,保证了主线程的安全,避免SharedPreferences在主线程可能会引发ANR问题。
总之,它克服了 SharedPreferences 的一些缺点,解决了 SharedPreferences API 的设计缺陷。

特性与技术要点

Jetpack DataStore是以 Kotlin协程Flow功能为基础提供了两种方式:

  • Proto DataStore 存储类的对象,通过 protocol buffers
    对象序列化存储在本地。
  • Preferences DataStore键值对的形式存储在本地。(和SharedPreferences类似,基于 Flow实现的,不会阻塞主线程,保证类型安全)
  • 支持从SharedPreferences到DataStore的数据迁移。

SharedPreferences的缺点

其实SharedPreferences作为一种轻量级的数据存储方式,使用起来也非常方便,以键值对的形式存储在本地,初始化 SharedPreference 的时候,会将整个文件内容加载内存中,因此会带来以下问题:

  • 通过getXXX()获取值的形式,可能会导致主线程阻塞
  • SharedPreferences不能保证类型安全
  • SharedPreferences加载的数据会一直留在内存中,浪费内存
  • apply()方法虽然是异步的,可能会发生 ANR,在 8.0 之前和 8.0 之后实现各不相同
  • apply() 方法无法获取到操作成功或者失败的结果

(1)为什么getXXX()方法会导致主线程阻塞

因为getXXX()都是同步的,在主线程调用 get 方法时,同步方法内调用了 wait() 方法,会必须等待
getSharedPreferences()
方法开启的线程读取完数据完毕,才能继续往下执行,会导致主线程阻塞。如果数据量读取的小,并没有什么影响,如果读取的文件较大会导致主线程阻塞。

调用 getSharedPreferences() 方法,最终会调用
SharedPreferencesImpl#startLoadFromDisk() 方法开启一个线程异步读取数据。

(2)SharedPreferences不能保证类型安全

调用 getXXX() 方法的时候,可能会出现 ClassCastException 异常,因为使用相同的 key
进行操作的时候,putXXX 方法可以使用不同类型的数据覆盖掉相同的 key。

(3)SharedPreferences加载的数据会一直留在内存中

通过 getSharedPreferences() 方法加载的数据,最后会将数据存储在静态的成员变量中。 通过静态的 ArrayMap
缓存每一个 SharedPreferences文件,而每个 SharedPreferences文件内容通过 Map
缓存键值对数据,这样数据会一直留在内存中,浪费内存。

(4)apply()方法是异步的,可能会发生ANR

apply() 方法是异步的,本身是不会有任何问题,但是当生命周期处于 handleStopService() 、
handlePauseActivity() 、 handleStopActivity() 的时候会一直等待 apply()
方法将数据保存成功,否则会一直等待,从而阻塞主线程造成 ANR。

DataStore带来了哪些改变呢?


与其说DataStore相对SharedPreference的改变,不如说是Preferences DataStore,因为Preferences DataStore主要是替换SharedPreference的,并且解决了SharedPreference所有问题

  • DataStore 是基于 Flow 实现的,所以保证了在主线程的安全性
  • 以事务方式处理更新数据,事务有四大特性(原子性、一致性、 隔离性、持久性)
  • 没有 apply() 和 commit() 等等数据持久的方法
  • 自动完成 SharedPreferences 迁移到 DataStore,保证数据一致性,不会造成数据损坏
  • 可以监听到操作成功或者失败结果

注意:

Preferences DataStore 只支持 Int , Long , Boolean , Float , String
键值对数据,适合存储简单、小型的数据,并且不支持局部更新,如果修改了其中一个值,整个文件内容将会被重新序列化,可以运行
AndroidX-Jetpack-Practice/DataStoreSimple 体验一下,如果需要局部更新,建议使用 Room。

附上一张 Google 分析的 SharedPreferences 和 DataStore 的区别:
在这里插入图片描述
附上一张 MMKV、DataStore、SharedPreferences的区别
在这里插入图片描述


工具类的封装

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "data")

object DataStoreUtils {

    /**
     * 保存数据
     * */
    suspend fun <T : Any> put(context: Context, key: String, value: T) {
        context.dataStore.edit { setting ->
            when (value) {
                is Int -> setting[intPreferencesKey(key)] = value
                is Long -> setting[longPreferencesKey(key)] = value
                is Double -> setting[doublePreferencesKey(key)] = value
                is Float -> setting[floatPreferencesKey(key)] = value
                is Boolean -> setting[booleanPreferencesKey(key)] = value
                is String -> setting[stringPreferencesKey(key)] = value
                else -> throw IllegalArgumentException("This type can be saved into DataStore")
            }
        }
    }
    /**
     * 获取数据
     * */
    suspend inline fun < reified T : Any> get(context: Context, key: String): T {
        return  when (T::class) {
            Int::class -> {
                context.dataStore.data.map { setting ->
                    setting[intPreferencesKey(key)] ?: 0
                }.first() as T
            }
            Long::class -> {
                context.dataStore.data.map { setting ->
                    setting[longPreferencesKey(key)] ?: 0L
                }.first() as T
            }
            Double::class -> {
                context.dataStore.data.map { setting ->
                    setting[doublePreferencesKey(key)] ?:0.0
                }.first() as T
            }
            Float::class -> {
                context.dataStore.data.map { setting ->
                    setting[floatPreferencesKey(key)] ?:0f
                }.first() as T
            }
            Boolean::class -> {
                context.dataStore.data.map { setting ->
                    setting[booleanPreferencesKey(key)]?:false
                }.first() as T
            }
            String::class -> {
                context.dataStore.data.map { setting ->
                    setting[stringPreferencesKey(key)] ?: ""
                }.first() as T
            }
            else -> {
                throw IllegalArgumentException("This type can be get into DataStore")
            }
        }
    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DataStore是一个新的异步API,用于在Kotlin中存储和读取数据。它使用Kotlin协程和Flow来实现异步操作,并在单独的线程上运行,从而保证了线程安全性。DataStore提供了结构化的错误处理、类型安全以及存储自定义复杂或大型数据类对象的支持。 要使用DataStore,首先需要获取DataStore对象。可以通过使用Kotlin委托来实现,具体如下所示: ```kotlin private val settingsDataStore by preferencesDataStore(name = "app_settings") ``` 在上述代码中,`settingsDataStore`是一个DataStore对象,它使用了`preferencesDataStore`委托来获取。`name`参数指定了DataStore的名称,可以是任何字符串,例如"app_settings"或包名称等。 一旦获取了DataStore对象,就可以使用它来读取和写入数据。以下是一些示例代码: ```kotlin // 写入数据 settingsDataStore.edit { settings -> settings[KEY_NAME] = "John" settings[KEY_AGE] = 25 } // 读取数据 val nameFlow: Flow<String?> = settingsDataStore.data.map { settings -> settings[KEY_NAME] } // 监听数据变化 settingsDataStore.data .map { settings -> settings[KEY_AGE] } .distinctUntilChanged() .onEach { age -> // 处理数据变化 } .launchIn(lifecycleScope) ``` 上述代码中,`edit`函数用于写入数据,`data`属性用于读取数据。可以使用`map`和`distinctUntilChanged`等函数对数据进行转换和过滤。`launchIn`函数用于在协程作用域中启动数据监听。 总结一下,DataStore是一个用于存储和读取数据的异步API,它提供了线程安全、结构化的错误处理、类型安全以及存储自定义复杂或大型数据类对象的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值