首页 > 编程知识 正文

datastore在哪,datastage入门

时间:2023-05-04 10:05:31 阅读:234329 作者:3912

文章目录 0. 简介1. SP的缺点2. DataStore的基础用法a. DataStore怎么写b. DataStore怎么读 3. 从SP迁移到DataStore4. DataStore的封装类5. 个人结论

0. 简介

Google在推出JetPack组件以来,一直推荐我们使用DataStore组件替代到我们第一天学android就知道的SharedPreferences组件,原因很简单,因为当年的SharedPreferences存在居多的问题,DataStore就是为了解决这些问题而来的。

1. SP的缺点

至于 SP到底存在哪些问题,我们可以直接查看 DataStore源码上的注释:

Synchronous API encourages StrictMode violationsapply() and commit() have no mechanism of signalling errorsapply() will block the UI thread on fsync()Not durable – it can returns state that is not yet persistedNo consistency or transactional semanticsThrows runtime exception on parsing errorsExposes mutable references to its internal state

用我们蹩脚的英语逐字逐句的翻译一遍:

同步的API鼓励违反StrictMode模式apply()和commit()方法没有错误信号机制apply()方法将界面重绘时会在阻塞UI线程不耐用 - 它可以返回状态,但是并不能将状态持久化没有一致性或者事物语义解析出现错误时,直接抛出运行时异常在其内部的状态中,暴露其可变的引用

老外写的问题直译过来一般都比较难懂,除非讲的很简单清楚。那么我就说两句人话,大概说一下我所认为的 SP所存在的问题吧:

不支持跨进程,使用 MODE_MULTI_PROCESS模式也没鸟用。而且在跨进程中,频繁的读写可能导致数据损坏或者丢失;懒加载模式下读取SP文件,可能会导致 getXXX() 阻塞。所以建议提前异步初始化 SP;sp文件的中的数据全部都保存在内存中,所以 SP对大数据量少儿不宜edit()方法每次都会新建一个bbdxgzImpl对象。建议一次edit(),多次putXXX;无论是 commit()还是 apply(),针对任何修改都是全量写入。这种情况下,对于高频的修改配置项存放在单独的SP文件中;commit()同步保存,有返回值;apply()异步保存,无返回值。onPause() onReceive()方法中使用异步写操作执行完成,可能会造成卡顿或者ANR。

当然这里并不是把SP贬得一无是处啊,正所谓存在即合理,当我们不涉及到跨进程,并且存储数据量比较少的情况下,SP还是相当不错的选择。

2. DataStore的基础用法

首先需要声明的一点是,DataStore存在两个版本的,一种是类似于SP,基于普通文件的读写;一种是基于Google protobuf模式的,这里的 protobuf是 Google自研的一种数据结构,平时用到的也比较少,我以前博客里面也写过类似的,这里就先只介绍第一种基于文件的逻辑:

下面介绍一下 DataStore的基础用法:

首先需要引入:

implementation("androidx.datastore:datastore:1.0.0")

首先需要明确一点,既然我们的DataStore是兼容当前使用的SP的,那么它就应该支持SP的存储类型,而且我们也知道SP支持的数据类型为Int,Long,Float,Boolean,String和StringSet;此时DataStore不仅支持以上六种数据结构,还支持一种额外的Double类型。

创建一个DataStore对象:

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

首先我们需要读取一个Int型的对象:

val keyName = intPreferencesKey("example_counter")val keyNameFlow: Flow<Int> = context.dataStore.data .map { preferences -> preferences[keyName] ?: 0}

然后我们写入一个 Int型的对象:

val keyName = intPreferencesKey("example_counter")suspend fun incrementCounter() { context.dataStore.edit { settings -> val currentCounterValue = settings[keyName] ?: 0 settings[keyName] = currentCounterValue + 1 }}

第一眼看上去很懵逼,写的什么玩意儿啊。没错,我学 DataStore的第一天也是这么想法,SP比这玩意儿香一万倍都不止啊,这么多新的东西我不知道,而且感觉写起来也是很拉跨,写一个简单的存储这么多代码。

首先,的确一个新的知识点出来,大家内心肯定是抗拒的,因为要去理解和实践,这个本身就比较耗时间和精力。但是大家都一样,所以还是需要去迎接变化。

首先,我们按照简单的来,我们都知道SP是基于XML文件的Key-Value结构,那么 DataStore作为它的兼容类,也必然兼容这种Key-Value结构。那么DataStore的Key是String型的么?然而并不是,它是一种Preferences.Key类型,具体类型为:androidx.datastore.preferences.core.Preferences.Key, 可以分为以下几种类型:

intPreferencesKey -> Preferences.Key<Int> 保存Int类型数据doublePreferencesKey -> Preferences.Key<Double> 保存Double类型数据stringPreferencesKey -> Preferences.Key<String> 保存String类型数据booleanPreferencesKey ->Preferences.Key<Boolean> 保存Boolean类型数据floatPreferencesKey -> Preferences.Key<Float> 保存Float类型数据longPreferencesKey -> Preferences.Key<Long> 保存Long类型数据stringSetPreferencesKey -> Preferences.Key<Set<String>> 保存Set<String>类型数据

有了Key之后,我们需要看看DataStore如果存储和读取数据的。

a. DataStore怎么写

SP有bbdxgz,同理DataStore也有edit方法:

public suspend fun DataStore<Preferences>.edit( transform: suspend (MutablePreferences) -> Unit): Preferences { return this.updateData { // It's safe to return MutablePreferences since we freeze it in // PreferencesDataStore.updateData() it.toMutablePreferences().apply { transform(this) } }}

首先它是一个suspend函数,只能在协程体中运行,每当遇到 suspend函数以挂起的方式运行时,并不会阻塞主线程运行。

既然是suspend函数,那么我们就可以有同步和异步的方式对数据进行写入:

同步方式:

private suspend fun saveSyncIntData(key : String, value:Int) { globalDataStore.edit { mutablePreferences -> mutablePreferences[intPreferencesKey(key)] = value }}

异步方式:
这个就很简单了,可以随意发挥了,在同步方法上套一个runBlocking就行了:

private fun saveIntData(key: String, value:Int) = runBlocking { saveSyncIntData(key,value) } b. DataStore怎么读

按照以上的惯例,肯定也会存在同步读取和异步读取的两种方法。首先需要明确一点,DataStore的data返回的是Flow类型,Flow是一种流式接口,类似于RxJava中的 Observable那样,存在很多操作符可以对数据进行变换,时间允许的情况下,可以写一篇关于Flow文章。

首先我们获取到同步的读:

private fun readSyncIntData(key: String, defaultValue: Int) : Flow<Int> = dataStore.data.catch { if(it is IOException) { it.printStackTrace() emit(emptyPreferences()) } else { throw it } }.map { it[intPreferencesKey(key)] ?: defaultValue }

对代码进行解读一下, dataStore.data返回类型是Flow类型,对Flow进行catch检查是否存在异常,然后map转换一下,然后得到Flow<Int>,最终并返回。

写完了同步的读,那么异步的读为:

private fun readIntData(key: String, defaultValue : Int) : Int { var resultValue = defaultValue runBlocking { dataStore.data.first { resultValue = it[intPreferencesKey(key)] ?: resultValue true } } return resultValue}

异步的读取直接返回了具体类型的数据,这里的first操作符是取第一个的意思。

基本上,我们对DataStore的操作有了一个简单的了解,重要的还是自己去实践,不难也不算容易。

3. 从SP迁移到DataStore

大概也就分两个步骤:

需要一个 SharedPreferencesMigration,这个迁移类并不算难,也就需要你传入Context和SP的文件名即可: val Context.dataStore : DataStore<Preferences> by preferencesDataStore(name = "dataStore_setting", produceMigrations = { context -> listOf( SharedPreferencesMigration(context, "sp_name")) }) 当dataStore生成完成之后,需要执行一个读或者写的操作,SharedPreferences的数据将会被迁移到dataStore中,同时SharedPreferences文件也将会被删除。

使用SP的文件夹

迁移到DataStore的文件夹

可以看到SP文件被删除了,然后dataStore的文件目录为/data/data/package_name/files/xxx.preferences_pb

4. DataStore的封装类

为了方便操作,我这边封装了DataStore的逻辑,读写起来会更方便一点,方法部分代码为:

我们使用时也很简单,直接代码为:

DataStoreUtils.putData("int_value",100)DataStoreUtils.putData("long_value",100L)DataStoreUtils.putData("float_value",100.0f)DataStoreUtils.putData("double_value",100.00)DataStoreUtils.putData("boolean_value",true)DataStoreUtils.putData("string_value","hello world")val intValue = DataStoreUtils.getData("int_value", 0)val longValue = DataStoreUtils.getData("long_value", 0L)val floatValue = DataStoreUtils.getData("float_value", 0.0f)val doubleValue = DataStoreUtils.getData("double_value", 0.00)val booleanValue = DataStoreUtils.getData("boolean_value", false)val stringValue = DataStoreUtils.getData("string_value", "hello")

当然,这只是异步的读取/存储方式,当然我们还有同步的获取方式:

lifecycle.coroutineScope.launch { // 读取 DataStoreUtils.getSyncData("int_value",0).collect(object : FlowCollector<Int> { override suspend fun emit(value: Int) { Log.d("TAG","get sync data : $value") } }) // 写入 DataStoreUtils.putSyncData("int_value", 1) }

当然,具体的源码可以看这个了.

5. 个人结论

总体来说,DataStore如果高度封装,其实使用方式上和SP基本上没什么区别,它解决了SP所存在的诟病,但是就目前而言,对它的性能还是未知的,这个可能需要后续的线上检验了,当然谷爹出品的东西,应该没什么太大的问题。当然了,学习DataStore其实对Kotlin还是有很高的门槛的,其中协程,高阶函数、Flow等相关知识点还是存在一个相当陡峭的学习坡度的。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。