再抱一抱DataStore

简介: DataStore 中使用到了 Flow 、协程,在使用的时候更适合现在的写法,但不能只因为他的写法简单就使用啊,目前即使DataStore 已经发布了 beta 版本,但是性能还是堪忧

一片爆红

在之前我发过一篇 DataStore 的文章:用力抱一下 Jetpack DataStore,当时使用的版本是1.0.0-alpha05

20210524100401171.png

大家也都知道 Google 的性格,在 alpha 版本的库的 API 随时可能修改,事实证明我的猜测是正确的,在更新了 DataStore 的版本之后果然一片爆红。

* init Context 
*@ param context Context 
 fun init ( context : Context ){
 dataStore = context . createDataStore ( preferenceName )
}
 fun readIntFlow ( key : String , default : Int =0): Flow < Int >=
 dataStore . data 
. Catch {
 this : FlowCollector < Preferences >
 if ( it is IoException ){
 it . printStackTrace ()
 emit ( emptyPreferences ())} else {
 throw it 
}. map { it : Preferences 
 it [ preferencesKey ( key )]?: default 
}
 fun readIntData ( key : String , default : Int =0): Int {
 var yalue =0
 runBlocking { this : CoroutineScope 
 dataStore . data . first { it : Preferences 
 value = it [ preferencesKey ( key )]?: default 
 true 
 first 
}
}
 return value 

但不要担心,发布了 beta 版本就证明 API 已经基本完善了,也就是说之后 API 就不会有大的改动了,剩下的就是性能方面的优化了。

开始使用

之前为了使用 DataStore 我还专门写了一个工具类,但现在看来是多余的了,来看看如何使用 beta 版本的 DataStore 吧。

添加依赖

第一步肯定要来添加下依赖。

// DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-beta01"

初始化 DataStore

在之前 alpha 版本中,初始化方法如下:

context.createDataStore(preferenceName)

通过 Context 的扩展方法 createDataStore 来创建一个 DataStore ,但现在不可以了,这个方法已经被删除,那么现在该如何创建 DataStore 呢?现在应该使用由 preferencesDataStore 创建的属性委托来创建 Datastore<Preferences> 实例。

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

在项目中只在顶层调用一次 preferencesDataStore 方法,便可在应用的所有其余部分通过此属性访问该实例,这样可以更轻松地将 DataStore 保留为单例。

下面咱们来看看 preferencesDataStore 的源码吧。

public fun preferencesDataStore(
    name: String,
    corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
    produceMigrations: (Context) -> List<DataMigration<Preferences>> = { listOf() },
    scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): ReadOnlyProperty<Context, DataStore<Preferences>> {
    return PreferenceDataStoreSingletonDelegate(name, corruptionHandler, produceMigrations, scope)
}

可以看到 preferencesDataStore 有四个参数,其中只有名字是必选参数,剩下的参数都有对应的默认值,下面就来看看这几个参数的含义吧。

  • name: DataStore 的名字,不多说;
  • corruptionHandler:如果DataStore在尝试读取数据时遇到CorruptionException,则将调用destroyHandler。无法对数据进行反序列化时,序列化程序会引发CorruptionException;
  • produceMigrations:产生迁移。 ApplicationContext作为参数传递给这些回调。在对数据进行任何访问之前,都要运行DataMigrations。不管是否成功,每个生产者和迁移都可以运行一次以上(可能是因为另一个迁移失败或对磁盘的写入失败)。
  • scope:作用域

上面介绍了 preferencesDataStore 方法中的参数,大家可以根据需求自行填写使用。

使用DataStore

创建 DataStore

使用的时候首先需要调用下咱们刚定义的 Context 的扩展属性:dataStore。

private lateinit var dataStore: DataStore<Preferences>
/**
 * init Context
 * @param context Context
 */
fun init(context: Context) {
    dataStore = context.dataStore
}

之后就可以通过dataStore进行操作了。

保存数据

先来看看如何进行数据的保存。

suspend fun saveBooleanData(key: String, value: Boolean) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[booleanPreferencesKey(key)] = value
    }
}
suspend fun saveIntData(key: String, value: Int) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[intPreferencesKey(key)] = value
    }
}
suspend fun saveStringData(key: String, value: String) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[stringPreferencesKey(key)] = value
    }
}
suspend fun saveFloatData(key: String, value: Float) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[floatPreferencesKey(key)] = value
    }
}
suspend fun saveLongData(key: String, value: Long) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[longPreferencesKey(key)] = value
    }
}

大家在使用的时候可以根据不同类型来分别进行保存,当然了,如果你不想分的这么细,也可以写一个方法来进行统一调用。

suspend fun <U> putData(key: String, value: U) {
    when (value) {
        is Long -> saveLongData(key, value)
        is String -> saveStringData(key, value)
        is Int -> saveIntData(key, value)
        is Boolean -> saveBooleanData(key, value)
        is Float -> saveFloatData(key, value)
        else -> throw IllegalArgumentException("This type can be saved into DataStore")
    }
}

读取数据

保存完数据就该进行读取了,来看看如何读取数据吧。

fun readBooleanFlow(key: String, default: Boolean = false): Flow<Boolean> =
    dataStore.data
        .catch {
            //当读取数据遇到错误时,如果是 `IOException` 异常,发送一个 emptyPreferences 来重新使用
            //但是如果是其他的异常,最好将它抛出去,不要隐藏问题
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[booleanPreferencesKey(key)] ?: default
        }
fun readIntFlow(key: String, default: Int = 0): Flow<Int> =
    dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[intPreferencesKey(key)] ?: default
        }
fun readStringFlow(key: String, default: String = ""): Flow<String> =
    dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[stringPreferencesKey(key)] ?: default
        }
fun readFloatFlow(key: String, default: Float = 0f): Flow<Float> =
    dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[floatPreferencesKey(key)] ?: default
        }
fun readLongFlow(key: String, default: Long = 0L): Flow<Long> =
    dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[longPreferencesKey(key)] ?: default
        }

同样的,根据类型不同选择不同的读取方法进行使用即可,当然了,也可以像上面保存数据一样再写一个方法进行统一调用也可以。

fun <U> getData(key: String, default: U): Flow<U> {
    val data = when (default) {
        is Long -> readLongFlow(key, default)
        is String -> readStringFlow(key, default)
        is Int -> readIntFlow(key, default)
        is Boolean -> readBooleanFlow(key, default)
        is Float -> readFloatFlow(key, default)
        else -> throw IllegalArgumentException("This type can be saved into DataStore")
    }
    return data as Flow<U>
}

进阶使用

上面已经描述了如何简单使用 DataStore ,但只能使用一些基础类型,如果咱们想要使用数据类呢?当然可以使用 DataStore 所支持的 Proto,但这里咱们不说这个,应为在 Kotlin 中也可以直接通过数据类序列化的方式来使用 DataStore ,来看看如何使用吧。

定义数据类

首先咱们来定义一个数据类吧。

data class ZhuPreferences(
    val name: String,
    val age: Int
)

数据类很简单,只有两个参数,名字和年龄。

实现 DataStore 序列化器

下一步需要做的是来实现 DataStore 的序列化器。

@Serializable
data class ZhuPreferences(
    val name: String = "jiang",
    val age: Int = 20
)
object ZhuPreferencesSerializer : Serializer<ZhuPreferences> {
    override val defaultValue = ZhuPreferences()
    override suspend fun readFrom(input: InputStream): ZhuPreferences {
        try {
            return Json.decodeFromString(
                ZhuPreferences.serializer(), input.readBytes().decodeToString()
            )
        } catch (serialization: SerializationException) {
            throw CorruptionException("Unable to read UserPrefs", serialization)
        }
    }
    override suspend fun writeTo(t: ZhuPreferences, output: OutputStream) {
        output.write(Json.encodeToString(ZhuPreferences.serializer(), t).encodeToByteArray())
    }
}

由于 Parcelables 与 DataStore 一起使用并不安全,因为不同 Android 版本之间的数据格式可能会有所变化,所以这里使用 Serializable 的方式进行序列化。

使用 DataStore 序列化器

创建 DataStore

数据类都准备好了,下面来创建 DataStore ,这里不能使用上面提到的 preferencesDataStore 方法来进行创建了,而是需要通过 dataStore 方法。

val Context.dataStores by dataStore("test", serializer = ZhuPreferencesSerializer)

写入数据

来看看如何写入数据类的数据吧。

private suspend fun setDataStore(zhuPreferences: ZhuPreferences) {
    dataStores.updateData {
        it.copy(name = zhuPreferences.name, age = zhuPreferences.age)
    }
}

从上面代码中可以看到通过 dataStores 中的 updateData 方法来进行修改数据,然后再使用生成的 .copy() 函数更新数据。

读取数据

写入完成之后来看看如何进行读取数据吧。

private suspend fun getDataStore() {
    val name = dataStores.data.first().name
    val age = dataStores.data.first().age
}

由于在创建 dataStores 的时候已经传入了序列化器,所以读取数据很简单,直接可以调取数据类中的参数进行使用。

总结

DataStore 中使用到了 Flow 、协程,在使用的时候更适合现在的写法,但不能只因为他的写法简单就使用啊,目前即使DataStore 已经发布了 beta 版本,但是性能还是堪忧,但我相信 DataStore 在正式版本发布的时候性能应该会追上甚至超越 MMKV 和 SP 。



目录
相关文章
|
4月前
|
域名解析 网络协议 网络性能优化
为什么需要NQA?NQA到底为何物?一文告诉你!
为什么需要NQA?NQA到底为何物?一文告诉你!
108 1
|
7月前
|
Dubbo Java 中间件
探寻源码宝藏:介绍开源项目"source-code-hunter"
最近处于金三银四的面试黄金期,许多同学在面试中反映现在要求非常高,阅读源码几乎是必问项。然而,阅读源码时常常觉得晦涩难懂,令人头疼。今天在浏览 GitHub 时,我发现了一个名为 source-code-hunter 的宝藏项目。这个项目从源码层面深入剖析和挖掘互联网行业主流技术的底层实现原理,为广大开发者提供了便利,助其提升技术深度。目前该项目已经涵盖了 Spring 全家桶、Mybatis、Netty、Dubbo 框架,以及 Redis、Tomcat 等中间件的内容,恰好适合最近正在面试或希望提升技术深度的同学参考学习。
782 1
探寻源码宝藏:介绍开源项目"source-code-hunter"
|
7月前
|
XML Android开发 数据格式
Dialog里面用ComposeView竟会直接闪退?深挖Lifecycle与Compose的爱恨情仇
Dialog里面用ComposeView竟会直接闪退?深挖Lifecycle与Compose的爱恨情仇
320 0
|
存储 缓存 NoSQL
Derek解读Bytom源码-持久化存储LevelDB
作者:Derek 简介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 本章介绍Derek解读-Bytom源码分析-持久化存储LevelDB 作者使用MacOS操作系统,其他平台也大同小异 Golang Version: 1.8 LevelDB介绍 比原链默认使用leveldb数据库。
1992 0
|
消息中间件 运维 API
金鱼哥RHCA回忆录:CL210管理计算资源--课外普及之Heat组件详解
第七章 管理计算资源--课外普及之Heat组件详解
195 0
金鱼哥RHCA回忆录:CL210管理计算资源--课外普及之Heat组件详解
|
小程序 Java API
【开源项目】小程序版 玩安卓
【开源项目】小程序版 玩安卓
112 0
【开源项目】小程序版 玩安卓
DHL
|
存储 JSON Ubuntu
[Google]再见SharedPreferences拥抱DataStore (二)
今天这篇文章主要来介绍 Proto DataStore,Proto DataStore 通过 protocol buffers 将对象序列化存储在本地,所以首先需要安装 Protobuf 编译 proto 文件,Protobuf 编译大致分为 Gradle 插件编译和命令行编译
DHL
321 0
[Google]再见SharedPreferences拥抱DataStore (二)
|
程序员 C++ 知识图谱
卧槽!这些笑死人不偿命的代码,你见识过吗?
卧槽!这些笑死人不偿命的代码,你见识过吗?
164 0
卧槽!这些笑死人不偿命的代码,你见识过吗?
|
存储 安全 测试技术
DataStore —— SharedPreferences 的替代者 ?
DataStore —— SharedPreferences 的替代者 ?
|
存储 监控 Oracle
外行假装内行,我也来谈谈SAP BAPI和BADI
外行假装内行,我也来谈谈SAP BAPI和BADI