再抱一抱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 。



目录
相关文章
|
7月前
|
关系型数据库 MySQL 分布式数据库
你常听说的WAL到底是什么
你常听说的WAL到底是什么
307 2
|
6月前
|
JavaScript 数据挖掘 程序员
老程序员分享:Piwik学习
老程序员分享:Piwik学习
|
7月前
|
Dubbo Java 中间件
探寻源码宝藏:介绍开源项目"source-code-hunter"
最近处于金三银四的面试黄金期,许多同学在面试中反映现在要求非常高,阅读源码几乎是必问项。然而,阅读源码时常常觉得晦涩难懂,令人头疼。今天在浏览 GitHub 时,我发现了一个名为 source-code-hunter 的宝藏项目。这个项目从源码层面深入剖析和挖掘互联网行业主流技术的底层实现原理,为广大开发者提供了便利,助其提升技术深度。目前该项目已经涵盖了 Spring 全家桶、Mybatis、Netty、Dubbo 框架,以及 Redis、Tomcat 等中间件的内容,恰好适合最近正在面试或希望提升技术深度的同学参考学习。
779 1
探寻源码宝藏:介绍开源项目"source-code-hunter"
|
存储 iOS开发 UED
PhotoKit初用
我们公司做了一个DLNA的投屏软件,但是iOS是不能跨应用访问数据的,所以对于局域网投屏视频和图片需要把图片或者视频写入到应用的沙盒路径下。 在我之前的前辈用的是AssetsLibrary,他是在进入界面之前写入,等到完全都写完了才会去显示。之前拍照的照片大小不是很大,而且手机的存储空间也不大,对于用户来说这么处理完全是没有问题的。但是,后来有用户反馈说在本地媒体界面一直都有那个“菊花转”。后来我们发现可能是用户的本地媒体数据过于大,倒是程序假死。 我们老大说,你把这个功能优化一下,目标就是像微信那样是最好的。后来我们就选用的PhotoKit这个框架。
|
存储 缓存 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数据库。
1989 0
|
设计模式 缓存 JSON
《看完它面试必solo | 寻找C站宝藏》
《看完它面试必solo | 寻找C站宝藏》
《看完它面试必solo | 寻找C站宝藏》
|
存储 监控 Oracle
外行假装内行,我也来谈谈SAP BAPI和BADI
外行假装内行,我也来谈谈SAP BAPI和BADI
|
SQL Web App开发
艾伟:从别人那拷下来的几点Session使用的经验(转载)
问:当页面中是否了frameset,发现在每个frame中显示页面的SessionID在第一次请求时都不相同,为什么?答:原因是你的frameset是放在一个htm页面上而不是ASPX页面。在一般情况下,如果frameset是aspx页面,当你请求页面时,它首先将请求发送到Web服务器,此时已经获得了SessionID,接着浏览器会分别请求Frame中的其他页面,这样所有页面的SessionID就是一样的,就是FrameSet页面的SessionID。
882 0
|
存储
分布式系统的烦恼------《Designing Data-Intensive Applications》读书笔记11
使用分布式系统与在单机系统中处理问题有很大的区别,分布式系统带来了更大的处理能力和存储容量之后,也带来了很多新的"烦恼"。在这一篇之中,我们将看看分布式系统带给我们新的挑战。
1283 0
|
机器学习/深度学习 安全 网络安全
听说Tech Insight在北京城又火了一把?
即将在12月19日国家会议中心举办的Tech Insight是2017年最后一场,也是全年的压轴之作。此前Tech Insight已在北京、上海、广州、深圳、成都等地多次举办,场场爆满。本次北京站结合了历届Tech Insight现场用户反馈的需求,议程设置更加丰满。
2597 0