使用 kotlinx.serialization 和DataStore 实现更简单易用的缓存

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

前言

DataStore 是Android 官方Jetpack组件库的一个组件,一个简易的数据存储解决方案,指代取代SharedPreferences,支持Koltin 协程和Flow,让应用能够以异步的方式存储和使用数据。

官方推荐两种使用方式,Preferences DataStoreProto DataStorePreferences 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.

Guess you like

Origin juejin.im/post/7035121434590445575