这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战
前言
DataStore
是Android 官方Jetpack
组件库的一个组件,一个简易的数据存储解决方案,指代取代SharedPreferences
,支持Koltin
协程和Flow
,让应用能够以异步的方式存储和使用数据。
官方推荐两种使用方式,Preferences DataStore
和Proto DataStore
。Preferences DataStore
使用比较简单,不需要预先定义,但是不支持类型安全。Proto DataStore
使用起来比较复杂,需要预先使用protocol buffers定义数据,但是类型安全。所以可不可以既不需要实现定义又能保证类型安全呢。kotlinx.serialization
给我们提供了一种解决方案。
缓存
如果一个页面需要加快显示,我们就需要对这个页面的数据做缓存。以前的方案是一般对接口响应的数据做缓存,在页面展示时重新parse 一遍缓存数据在做展示,暂不讨论性能问题的话,这样子做有两个比较明显的问题,一是需要比较多的样板代码,而是往往接口的数据并不是完整的数据,我们往往需要从多个接口或者组合App 中现有的数据才能得到最终显示的数据,这部分状态数据是采用这种缓存方案无法取到的,所以我们是不是可以直接缓存UI显示需要的数据呢?
使用kotlinx.serialization
序列化数据
在使用Kotlin开发时,我们经常把状态封装到数据类中,状态驱动View 显示,View 拿到数据类就可以直接显示了。
sealed class PageState {
object Loading : PageState()
data class Error(val cause: Throwable) : PageState()
data class RenderData(
val modules: List<ModuleModel>
) : PageState()
}
复制代码
kotlinx.serialization
是kotlin官方的序列化库,主要优点是基于@serializable
注解,编译器在编译阶段就生成了序列化代码从而避免运行时的反射开销。
kotlinx.serialization
可以方便的对数据类进行序列化和反序列化,所以考虑是不是可以使用这个技术简化缓存过程呢?
kotlinx.serialization
使用起来也很简单,最简单的使用方法,我们只要在目标数据类上加上
@Serializable``
注解即可。
@Serializable
data class RenderData(
val modules: List<ModuleModel>
) : PageState()
复制代码
序列化使用起来非常简单
val bytes = Json.encodeToString(renderData).encodeToByteArray()
复制代码
反序列化用起来代码也很少
val renderData = Json.decodeFromString<PageState.RenderData>(inputStream.readBytes().decodeToString())
复制代码
使用 kotlinx.serialization
需要引入相关依赖。
- Project 的buidld.gradle 引入相关插件配置
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
复制代码
- Module 中的build.gradle引入插件和依赖
plugins {
id 'kotlinx-serialization'
}
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0"
}
复制代码
使用DataStore 缓存数据
但是仅仅序列化和反序列化还不够,存储和读取也是一推样本代码,恰好DataStore可以帮我们简化这个过程,我们仅仅需要定义一个Serializer
传给DataStore告诉其如何序列化和反序列化就行了。
private object RenderDataSerializer :
androidx.datastore.core.Serializer<PageState.RenderData> {
override val defaultValue: PageState.RenderData
get() = RENDER_DATA_NULL
override suspend fun readFrom(input: InputStream): PageState.RenderData {
return try {
Json.decodeFromString(input.readBytes().decodeToString())
} catch (serialization: SerializationException) {
logger.all( "readFrom: err: ${serialization.message}")
defaultValue
}
}
override suspend fun writeTo(t: PageState.RenderData, output: OutputStream) {
try {
withContext(Dispatchers.IO) {
output.write(Json.encodeToString(t).encodeToByteArray())
}
} catch (ex: Exception) {
logger.all( "writeTo: ${ex.message}")
}
}
}
复制代码
一个简单的框架如下
object PageModuleStore{
private val RENDER_DATA_NULL = PageState.RenderData(emptyList())
private val Context.dataStore by dataStore(
fileName = "page_module.json",
serializer = RenderDataSerializer
)
suspend fun getPageCache(): PageState.RenderData? {
return try {
val data = MyApp.getAppContext().dataStore.data.first()
return if (data == RENDER_DATA_NULL) {
null
} else {
data
}
} catch (ex: Exception) {
null
}
}
suspend fun savePageCache(data: PageState.RenderData): Boolean {
return try {
MyApp.getAppContext().dataStore.updateData { data }
logger.all( "savePageCache success: $data")
true
} catch (e: Exception) {
logger.all( "savePageCache failed: ${e.message}")
false
}
}
suspend fun clearPageCache() {
logger.all( "clearPageCache.")
savePageCache(RENDER_DATA_NULL)
}
private object RenderDataSerializer :
androidx.datastore.core.Serializer<PageState.RenderData> {
override val defaultValue: PageState.RenderData
get() = RENDER_DATA_NULL
override suspend fun readFrom(input: InputStream): PageState.RenderData {
return try {
Json.decodeFromString(input.readBytes().decodeToString())
} catch (serialization: SerializationException) {
logger.all( "readFrom: err: ${serialization.message}")
defaultValue
}
}
override suspend fun writeTo(t: PageState.RenderData, output: OutputStream) {
try {
withContext(Dispatchers.IO) {
output.write(Json.encodeToString(t).encodeToByteArray())
}
} catch (ex: Exception) {
logger.all( "writeTo: ${ex.message}")
}
}
}
}
复制代码
kotlinx.serialization
让我们可以忽略序列化和反序列化的细节,DataStore
帮我们实现了缓存的存储和使用。同时我们缓存的是最终数据,数据是带状态的,这样子的数据拿过来就可以用,也加快了界面的显示。
Happy ending.