JetPack家族DataStore库的那些使用记录点滴

最近项目中自己负责的模块有涉及到缓存的内容,因为项目中已经有了Sp的使用工具类,因为一直想对它进行改进,可是一直没有好的机会,这次是个契机,对DataStroe库进行了简单的使用,目前还在测试版本,还没有稳定版,但是用着还不错,本人先单一的在自己的负责的模块使用,等到稳定版发布再进行升级以及原有的Sp缓存数据的迁移,所以这里也是先做个简单的记录,里面也有自己的一些使用心得优化。

DataStore和Sp的对比,很多人还不是很明白,我也是简单的介绍一下吧,Sp的弊端我们知道它会阻塞主线程,如果内容比较大那么App的体验会变得迟钝,卡顿等现象,并且读取出来的内容还会一值在内存中,App会变得更卡;DataStore库是异步的,它不会阻塞主线程,我们看到它的方法使用都是带有supend关键字,说明它支持协程一起使用,使用的时候会挂起,不会阻塞主线程,这也是它的优点,用起来也是非常方便,我先上一下我自己的代码。

首先我们要创建缓存的对象:

var dataStore: DataStore<Preferences> = context.createDataStore(
        name = PREFERENCE_NAME
)

name是我们的文件名字,然后我们就可以使用了。

我利用单例进行了工具类的封装使用:

class DataStoreRepository(val context: Context) : IDataStoreRepository {

    private val PREFERENCE_NAME = "DataStore"

    companion object {
        @Volatile
        private var instance: IDataStoreRepository? = null
        fun getInstance() =
                instance ?: synchronized(this) {
                    instance ?: DataStoreRepository(OverAllConstant.context).also { instance = it }
                }

    }

IDataStoreRepository接口声明了我们能用到的读取操作方法:

interface IDataStoreRepository {

    suspend fun saveIntData(key: String, value: Int)

    suspend fun readIntData(key: String): Int

    fun migrationSP2DataStore()
}

首先介绍一下读取方法:

override suspend fun readIntData(key: String): Int {
    val KEY_PREFERENCES = preferencesKey<Int>("${key}${StateContext.getMemeberId()}")

    val value = dataStore.data.catch {
        if (it is IOException) {
            it.printStackTrace()
            emit(emptyPreferences())
        } else {
            throw it
        }
    }.map { preferences ->
        preferences[KEY_PREFERENCES] ?: 0
    }
    return value.first()
}

我这里只是拿了Int的读取方法来做案例。

val KEY_PREFERENCES = preferencesKey<Int>("${key}${StateContext.getMemeberId()}")

首先来说一下这行代码,我们发现DataStore库的key不是Sp那样的简单的字符串来进行标记,我们用对象进行包装的,preferencesKey<类型>(key:String)

我们知道在app里面不同的用户会进行不同缓存数据key进行标记,我们在根方法里面直接传入字符串进行preferencesKey的生成,所以我们看一下key的声明的类:

object PreferencesKeys {

    //原有的sp文件名字,用来和DataStore进行合并
    val PREFERENCE_NAME = "preferences"

    //消息未读数
    val KEY_MSG_UNREAD_NUM = "msg_unread_num"
}

很简单,跟以前的使用一样,直接声明常亮字符串即可,所以我们的变化就只能在跟方法里面自己去拿到用户的唯一标识来进行key的生成,这样就可以做到唯一了。

我们再接下来看,catch方法,为了防止问题产生,我们进行catch处理,当读取数据遇到错误的时候,如果是IO异常,那就发送一个空的preferences,来重新使用,如果是其他的异常就抛出去,不隐藏问题。

然后通过map方法来获取内容Folw<R>对象,最后通过first()方法返回Folw里面的内容,也就是我们最终读取到数值。

我们的方法都是用supend关键字来修饰,所以使用的地方都得在协程里面进行调用,首先我们写一个扩展函数:

fun BaseActivity.initiateCoroutineScope(
        block: suspend () -> Unit
) {
    lifecycleScope.launch {
        runCatching {
            block()
        }.onSuccess {
        }.onFailure {
            it.printStackTrace()
        }
    }
}

可见到方法里面我们还进行了catch处理,错误也会打印出来,那么我们在Activity里面使用的时候就很简单了,不用再重新写启动协程的代码,直接如下即可:

initiateCoroutineScope{
    val value = DataStoreRepository.getInstance().readIntData(PreferencesKeys.KEY_MSG_UNREAD_NUM)
}

是不是很简单呢,这也是扩展函数的好处,代码清晰易懂,还有效的缩短了代码行数,不忽悠冗余的代码。

存储也是很简单:

initiateDataStore {
    DataStoreRepository.getInstance().saveIntData(PreferencesKeys.KEY_MSG_UNREAD_NUM, 100)
}

我们只需要用我们写得扩展函数包装即可,直接调用save方法即可。

上一篇:引入Jetpack架构后,你的App会发生哪些变化


下一篇:jetpack加kotlin项目实践二 盗取Android官网架构指南