当前位置:   article > 正文

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

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")
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/309415?site
推荐阅读
相关标签
  

闽ICP备14008679号