DataStore是Jetpack中新增的数据存储解决方案,旨在取代 SharedPreferences。DataStore 基于 Kotlin 协程和 Flow 构建,以异步、一致的事务方式存储数据,并提供了以下两种不同的实现:
DataStore和MMKV、数据库的选择
如果要处理键值对存储,已经在用MMKV的推荐继续使用,如果没有接入MMKV,那么也推荐接入MMKV,毕竟性能好效率高。
或者使用Preferences DataStore,作为谷歌官方推出的SharedPreferences键值对存储替代方案,不过本文不多做描述。
对于PB协议数据本地化存储,MMKV除了常规基础类型,只支持实现了Parcelable的对象存储,Protocol Buffer的对象要进行存储并没有现成方案,而Proto DataStore正好可以解决这个问题。
对于性能,键值对存储DataStore的效率一定是比MMKV差的,但Proto DataStore存储PB对象的场景和MMKV没有什么可比性,所以不需过于关注此问题,优先考虑实现方便程度(比如类型检查问题,Proto DataStore可以在编译期显示错误提示,相比MMKV、SP等运行时崩溃,编译时错误提示更好让开发人员发现问题)。
所以在项目中,根据不同场景选用不同存储方式:
Protobuf 协议前后版本兼容问题
协议缓存版本为旧版,最新代码+服务器版本为新版,存在以下情况:
仅修改参数名,则新版代码也应该修改了该参数的获取方法名,否则报错找不到该属性 从缓存数据中仍能被读取出来(因为protobuf协议中,每个参数都对应一个编号protoDataStore实质上是根据序号进行存储的,即修改序号对应的参数名,该项的数据仍然能读取到)
修改参数类型,则新版代码也应该修改了该参数的使用类型,否则可能报错类型转换错误 从缓存中读取到的为该类型的默认值
可以看出,Proto DataStore遵循了Protobuf协议向前向后兼容的特性:对于新增或修改类型的参数(服务端一般不会修改已有参数的类型),从缓存中读取并不会引起错误崩溃,只需要我们处理好返回默认值的情况;而对于仅修改了名称的参数,甚至还能从缓存中读取到原先名字对应保存的数据。
这些特性都是专为存储Protobuf Buffer而存在的Proto DataStore所独有的,如果使用其他方案,比如将对象转为Byte存入MMVK,在取出并反序列化为对象后,还需要自行处理新旧对象间的差异,将是一件非常麻烦的事情。
接入和封装使用
引入项目gradle:
api('androidx.datastore:datastore:1.0.0-beta01') {
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core'
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
api('androidx.datastore:datastore-core:1.0.0-beta01') {
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
最新版本为1.0.0正式版,这里我选择了适用于自己项目内无需升级其它组件的版本为1.0.0-beta01,核心api都在
datastore-core
库中,datastore
主要增加了一些扩展方法,比如快速创建全局单一实例的dataStore对象,避免多个对象操作同一文件的问题。
常规使用方法:
创建全局单一实例DataStore对象
val Context.userInfoStore: DataStore<UserProto> by dataStore(
fileName = "userInfo.pb",
serializer = UserProtoSerializer
//创建序列化器
object UserProtoSerializer : Serializer<UserProto> {
override suspend fun readFrom(input: InputStream): UserProto {
try {
return UserProto.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
override suspend fun writeTo(t: UserProto, output: OutputStream) {
t.writeTo(output)
override val defaultValue: UserProto
get() = UserProto.getDefaultInstance()
写入和读取数据
private fun dataStoreProto() {
//将内容写入 Proto DataStore
lifecycleScope.launch {
context.userInfoStore.updateData {
it.toBuilder()
.setName("newName")
.setAge(18)
.build()
//读取数据
lifecycleScope.launch {
val user = userInfoStore.data.first()
封装后使用方法
封装后,只需在项目中DataStoreManager中声明要保存的DataStore实例和对象类型,比如此处我保存了个人信息和设置信息的协议对象数据:
object DataStoreManager {
//退出登录时清空缓存
fun clearAll() {
GlobalScope.launch {
dsMineInfo.update { it.toBuilder().clear().build() }
dsSettingInfo.update { it.toBuilder().clear().build() }
val dsMineInfo by lazy { ApplicationContext.getContext().dsMineInfo }
val dsSettingInfo by lazy { ApplicationContext.getContext().dsSettingInfo }
* 个人信息
private val Context.dsMineInfo by dataStore(
fileName = MineInfoProto::class.simpleName.toString(),
serializer = getSerializer(
defaultValue = MineInfoProto.getDefaultInstance(),
readFrom = { MineInfoProto.parseFrom(it) })
* 设置信息
private val Context.dsSettingInfo by dataStore(
fileName = SettingInfoProto::class.simpleName.toString(),
serializer = getSerializer(
defaultValue = SettingInfoProto.getDefaultInstance(),
readFrom = { SettingInfoProto.parseFrom(it) })
在业务场景对应处使用扩展方法读取数据,此处也可以使用Flow关联LiveData
override fun onCreate() {
super.onCreate()
GlobalScope.launch(Dispatchers.IO) {
val data = DataStoreManager.dsMineInfo.getValue()
//使用数据
在ViewModel中保存数据:
val data = //获取服务器协议数据
viewModel.viewModelScope.saveDataStore(DataStoreManager.dsMainInfo, data)
viewModel.viewModelScope.launch {
DataStoreManager.dsMainInfo.save(data)
具体封装方法:
* 保存数据
fun <T : GeneratedMessageLite> CoroutineScope.saveDataStore(
dataStore: DataStore<T>?,
data: T?,
block: ((Boolean) -> Unit)? = null
this.launch {
dataStore.save(data, block)
suspend fun <T : GeneratedMessageLite> DataStore<T>?.save(
data: T?,
block: ((Boolean) -> Unit)? = null
this?.let { dataStore ->
withContext(Dispatchers.IO) {
try {
val result = data?.let {
dataStore.updateData { data }
} ?: false
block?.let {
withContext(Dispatchers.Main) { it(result) }
} catch (e: Exception) {
Logz.tag("DataStoreUtil").e(e.message)
block?.let {
withContext(Dispatchers.Main) { it(false) }
* 更新数据
suspend fun <T : GeneratedMessageLite> DataStore<T>?.update(
block: ((Boolean) -> Unit)? = null,
update: ((T) -> T)
this?.let { dataStore ->
withContext(Dispatchers.IO) {
try {
dataStore.updateData {
update(it)
block?.let {
withContext(Dispatchers.Main) { it(true) }
} catch (e: Exception) {
Logz.tag("DataStoreUtil").e(e.message)
block?.let {
withContext(Dispatchers.Main) { it(false) }
* 读取数据
suspend fun <T : GeneratedMessageLite> DataStore<T>?.getValue(): T? {
return this?.let { dataStore ->
withContext(Dispatchers.IO) {
try {
val data = dataStore.data.first()
withContext(Dispatchers.Main) { data }
} catch (e: Exception) {
Logz.tag("DataStoreUtil").e(e.message)
withContext(Dispatchers.Main) { null }
* 读取Flow数据
inline fun <reified T : GeneratedMessageLite> DataStore<T>?.getValueAsFlow(): Flow<T>? {
return this?.let { dataStore ->
dataStore.data.catch { exception ->
if (exception is IOException) {
emit(T::class.java.newInstance())
} else {
throw exception
* 创建Protocol Buffer对应的Serializer
internal fun <T : GeneratedMessageLite> getSerializer(
defaultValue: T,
readFrom: (input: InputStream) -> T
): Serializer<T> {
return object : Serializer<T> {
override val defaultValue: T
get() = defaultValue
override suspend fun readFrom(input: InputStream): T {
try {
return readFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
override suspend fun writeTo(t: T, output: OutputStream) {
t.writeTo(output)
复制代码