Android使用DataStore保存数据之后断电重启设备数据丢失临时解决办法

前言:

DataStore 被推荐用来取代 SharedPreferences 后,我便将其应用于项目之中。然而在实际使用过程中,却遭遇了严重的问题:一旦发生立即断电重启的情况,数据不仅无法保存,甚至还会出现损坏且无法恢复的状况!这简直如同一场灾难。

通过 Google IssueTracker 进行查询后得知,这个问题自被发现之初至今,已然过去两年有余。从 DataStore1.0.0 版本直至 1.1.0 版本,该问题始终未得到解决,而且官网文档也未公布下一个版本的发布时间,这似乎意味着在短期内此问题都难以得到修复。不过,不得不说 DataStoreFlow 和协程配合使用时,所展现出的便利性是极具吸引力的,因此在当前阶段,我也不太愿意对其进行大规模的改动。


问题描述

在项目里,我只是采用了 DataStore 简单的 <Key,Value> 模式来存储用户首选项数据,本想着这样能方便又高效地完成数据存储任务。可谁能料到,在遇到立即断电这种情况时,却出现了严重的问题。不但新的数据没办法存储下来,更糟糕的是,还会致使其他原本正常的数据一并遭到损坏,并且这些损坏的数据根本没办法恢复,实在是让人头疼不已呀。

而且呢,下面相关的使用方法都是原原本本照着官网来操作的,按道理来说不应该出现问题才对,可偏偏就在立即断电重启这样的场景下,还是出现了故障,这着实让人有些无奈和困扰啊。


val Context.userSettingsDataStore by preferencesDataStore("user_settings")data class UserSettings(val isDark: Boolean)class UserSettingsRepository(context: Context) {private val dataStore = context.userSettingsDataStoreprivate object PreferencesKeys {val KEY_DARK = booleanPreferencesKey("is_dark")}val userSettingsFlow = dataStore.data.catch { ex ->if (ex is IOException) {emit(emptyPreferences())} else {throw ex}}.map {it[PreferencesKeys.KEY_DARK] ?: false}suspend fun setThemeDark(dark: Boolean) {dataStore.edit {it[PreferencesKeys.KEY_DARK] = dark}}
}

原因分析:

目前对于出现这种问题的原因,我暂时还没能想明白呀。总感觉导致这个问题出现的因素不止一处,可能涉及到多个方面的情况交织在一起了。而且我也向官方反馈了这个情况,可到现在官方都还没有给出任何回复呢,就只能这么干等着,心里实在没底,也不知道什么时候才能把这个棘手的问题给解决掉啊。


解决(临时解决)方案:

经过一番测试后发现,在面对立即断电重启这样的情况时,SharedPreferences 的表现相当稳定,数据既不会丢失,更不会出现损坏的情况。基于这个测试结果,我琢磨出了一个思路,那就是在使用 DataStore 进行数据存储的同时,也另外存储一份相同的数据到 SharedPreferences 当中。如此一来,等到下次启动应用的时候,就可以先从 SharedPreferences 里读取数据,然后再把这些数据重新写入到 DataStore 里面去。
可能有人会问了,既然都已经回过头去用 SharedPreferences 了,那干嘛还非要执着于使用 DataStore 呢?其实啊,重点就在于 DataStore 配合 Flow 来对流式监听数据变化这一功能真的是太好用了,仅凭这一点,就让我对 DataStore 依旧抱有一丝希望,盼着官方能够尽快修复它存在的这个问题呀。
以下就是我目前想到的临时解决办法:
我新建了一个名为 DataStoreBackup 的类,用它来替换掉原来 DataStoreeditupdateData 方法。在创建 DataStore 单例的时候呢,会从 SharedPreferences 中重新读取数据,通过这样的方式来尽量保证数据的完整性以及应用在应对断电重启等情况时的稳定性,虽然只是个临时举措,但也算是目前能想到的比较可行的办法了。


import android.content.Context
import androidx.annotation.GuardedBy
import androidx.datastore.core.DataMigration
import androidx.datastore.core.DataStore
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.mutablePreferencesOf
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import androidx.datastore.preferences.preferencesDataStoreFile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KPropertyfun preferencesDataStoreAndBackup(name: String,corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? =ReplaceFileCorruptionHandler {it.printStackTrace()emptyPreferences()},produceMigrations: (Context) -> List<DataMigration<Preferences>> = { listOf() },scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): ReadOnlyProperty<Context, DataStoreBackup> {return PreferenceDataStoreBackupSingletonDelegate(name,corruptionHandler,produceMigrations,scope)
}class DataStoreBackup(context: Context,name: String,private val dataStore: DataStore<Preferences>
) {private val sp by lazy {context.getSharedPreferences(name, Context.MODE_PRIVATE)}val data get() = dataStore.datasuspend fun edit(transform: suspend (MutablePreferences) -> Unit) {this.updateData(transform)}suspend fun updateData(transform: suspend (MutablePreferences) -> Unit) {dataStore.updateData {editBackup(transform)it.toMutablePreferences().apply {transform.invoke(this)}}}private suspend fun editBackup(transform: suspend (MutablePreferences) -> Unit) {val newData = mutablePreferencesOf()transform.invoke(newData)withContext(Dispatchers.IO) {val editor = sp.edit()newData.asMap().keys.forEach {val key = it.namewhen (val value = newData[it]) {is Boolean -> {editor.putBoolean(key, value)}is Long -> {editor.putLong(key, value)}is Int -> {editor.putInt(key, value)}is Float -> {editor.putFloat(key, value)}is String -> {editor.putString(key, value)}is Set<*> -> {@Suppress("UNCHECKED_CAST")editor.putStringSet(key, value as? Set<String> ?: emptySet())}}}editor.commit()}}}internal class PreferenceDataStoreBackupSingletonDelegate internal constructor(private val name: String,private val corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,private val produceMigrations: (Context) -> List<DataMigration<Preferences>>,private val scope: CoroutineScope
) : ReadOnlyProperty<Context, DataStoreBackup> {private val lock = Any()@GuardedBy("lock")@Volatileprivate var INSTANCE: DataStoreBackup? = null/*** Gets the instance of the DataStore.** @param thisRef must be an instance of [Context]* @param property not used*/override fun getValue(thisRef: Context, property: KProperty<*>): DataStoreBackup {return INSTANCE ?: synchronized(lock) {if (INSTANCE == null) {val applicationContext = thisRef.applicationContextval backupFileName = name + "_backup"val dataStore = PreferenceDataStoreFactory.create(corruptionHandler = corruptionHandler,migrations = produceMigrations(applicationContext),scope = scope) {applicationContext.preferencesDataStoreFile(name)}scope.launch(Dispatchers.IO) {val map = readBackupSharedPreferences(applicationContext, backupFileName)dataStore.edit {restorePreferencesFromBackup(map, it)}}INSTANCE = DataStoreBackup(applicationContext, backupFileName, dataStore)}INSTANCE!!}}private suspend fun readBackupSharedPreferences(appContext: Context,name: String): Map<String, *> {return withContext(Dispatchers.IO) {try {val sp = appContext.getSharedPreferences(name,Context.MODE_PRIVATE)sp.all} catch (e: Throwable) {emptyMap()}}}private fun restorePreferencesFromBackup(map: Map<String, *>,mutablePreferences: MutablePreferences) {map.keys.forEach { key ->when (val value = map[key]) {is Boolean -> mutablePreferences[booleanPreferencesKey(key)] = valueis Float -> mutablePreferences[floatPreferencesKey(key)] = valueis Int -> mutablePreferences[intPreferencesKey(key)] = valueis Long -> mutablePreferences[longPreferencesKey(key)] = valueis String -> mutablePreferences[stringPreferencesKey(key)] = valueis Set<*> -> {@Suppress("UNCHECKED_CAST")mutablePreferences[stringSetPreferencesKey(key)] = value as Set<String>}}}}
}

使用示例:

val Context.userSettingsDataStore by preferencesDataStoreAndBackup("user_settings")data class UserSettings(val isDark: Boolean)class UserSettingsRepository(context: Context) {private val dataStore = context.userSettingsDataStoreprivate object PreferencesKeys {val KEY_DARK = booleanPreferencesKey("is_dark")}val userSettingsFlow = dataStore.data.catch { ex ->if (ex is IOException) {emit(emptyPreferences())} else {throw ex}}.map {it[PreferencesKeys.KEY_DARK] ?: false}suspend fun setThemeDark(dark: Boolean) {dataStore.edit {it[PreferencesKeys.KEY_DARK] = dark}}
}

没错,代码方面的改动并不大呢。仅仅是把原本使用的 preferencesDataStore 替换成 preferencesDataStoreAndBackup 就行了,操作起来还挺简单的。快去测试一下,看看在经历断电重启这种情况后,数据到底能不能够成功存储,希望这个临时的解决办法能够帮你到你呢。


总结:

这种解决办法呢,确实存在一些缺点。
先说缺点的方面吧,它会导致双倍的存储时间,毕竟要同时往 DataStoreSharedPreferences 里存储数据呀,这无疑增加了数据存储所耗费的时长。不过好在它不会阻塞 UI,无论是读取数据还是写入数据,都是在协程中完成的,所以在操作过程中,用户界面不会出现卡顿之类的糟糕体验,这一点还是比较让人欣慰的。
而说到优点嘛,暂时还真没怎么发现呢,也不确定它到底有没有其他突出的优势,目前来看,它最大的作用就是解决了在立即断电重启的场景下数据无法存储的问题,从这个角度讲,也算是达到了我想要的最基本的效果了。
真心希望官方能够早点推出优化后的版本呀,这样就不用再采用这种临时的、略显笨拙的解决办法了。要是路过的大神们察觉到这个办法存在什么问题,还请不吝赐教呀,我就是个小白,很多地方还不太懂,要是能得到大家的指点,那可就太幸运了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/498366.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VuePress搭建个人博客

VuePress搭建个人博客 官网地址: https://v2.vuepress.vuejs.org/zh/ 相关链接: https://theme-hope.vuejs.press/zh/get-started/ 快速上手 pnpm create vuepress vuepress-starter# 选择简体中文、pnpm等, 具体如下 .../19347d7670a-1fd8 | 69 .../19…

Junit4单元测试快速上手

文章目录 POM依赖引入业务层测试代码Web层测试代码生成测试类文件 在工作中我用的最多的单元测试框架是Junit4。通常在写DAO、Service、Web层代码的时候都会进行单元测试&#xff0c;方便后续编码&#xff0c;前端甩锅。 POM依赖引入 <dependency><groupId>org.spr…

ABB RobotStudio学习记录(二)SmartGripper模拟

SmartGripper模拟 准备具体操作 准备 名称版本Robot Studio6.08 为了简化开发&#xff0c;我研究了 ABB 机械臂 SmartGripper 在 ABB RobotStudio 中的模拟操作。 具体操作 主要分3个步骤&#xff1a; 修改机械装置&#xff0c;设置Pose; 我这里使用的ABB YuMi&#xff0c…

terminal_学习

参考&#xff1a; 让你的 Mac 提前用上 macOS Catalina 的 Shell——Oh My Zsh 配置指南 https://sspai.com/post/55176MAC 终端美化教程&#xff08;来个全套 &#xff09;https://blog.csdn.net/weixin_42326144/article/details/121957795 x.1 zsh做美化&#xff08;安装oh…

音视频入门知识(四):封装篇

⭐四、封装篇 H264封装成mp4、flv等格式&#xff0c;那为什么需要封装&#xff1f; ​ h264也能播放&#xff0c;但是按照帧率进行播放&#xff0c;可能不准 ★FLV **FLV&#xff08;Flash Video&#xff09;**是一种用于传输和播放视频的容器文件格式。FLV 格式广泛应用于流媒…

使用 ASP.NET Core wwwroot 上传和存储文件

在 ASP.NET Core 应用程序中上传和存储文件是用户个人资料、产品目录等功能的常见要求。本指南将解释使用wwwroot存储图像&#xff08;可用于文件&#xff09;的过程以及如何在应用程序中处理图像上传。 步骤 1&#xff1a;设置项目环境 确保您的 ASP.NET 项目中具有必要的依…

S2-007-RCE(CVE-2012-0838)--vulhub

S2-007-RCE(CVE-2012-0838) 攻击者可以利用不安全的输入数据&#xff0c;构造OGNL表达式&#xff0c;最终导致服务器执行恶意命令。特别是在没有适当的输入验证或配置的情况下&#xff0c;攻击者可以在 HTTP 请求中嵌入 OGNL 表达式&#xff0c;触发远程代码执行。 Affected …

tar.gz压缩文件在linux上解压异常问题:gzip:stdin:invalid compressed data

1. 异常描述 将一个tar.gz压缩文件从windows拷贝到linux上之后&#xff0c;使用命令&#xff1a;tar -zxvf xxx.tar.gz压缩包时出现如下提示信息&#xff1a; 2. 异常分析 压缩包在下载的时候没有下载完整&#xff0c;重新下载一个试试。

UE5材质节点CameraDepthFade

相机深度消失&#xff0c;Fade Length相机离物体位置&#xff0c;Fade Offset消失偏移 可以让物体随着相机距离消失 相机深度消失 边缘自发光

Python机器学习笔记(十五、聚类算法的对比和评估)

用真实世界的数据集对k均值、凝聚聚类和DBSCAN算法进行比较。 1. 用真实值评估聚类 评估聚类算法对真实世界数据集的聚类结果&#xff0c;可以用调整rand指数ARI和归一化互信息NMI。 调整rand指数 &#xff08;adjusted rand index&#xff0c;ARI&#xff09;和归一化互信息…

SAP PP bom历史导出 ALV 及XLSX 带ECN号

bom总数 104W PS超过XLSX上限 &#xff0c;那就分文件 *&---------------------------------------------------------------------* *& Report ZRPT_PP_BOM_HIS_ECN *&---------------------------------------------------------------------* *& tcode:zpp0…

《代码随想录》Day20打卡!

《代码随想录》二叉树&#xff1a;二叉搜索树的最近公共祖先 本题的完整题目如下&#xff1a; 本题的思路如下&#xff1a; 1.之前写过一个二叉树的最近公共祖先&#xff0c;本题相比于另一道题&#xff0c;不同是本题是二叉搜索树&#xff0c;有一些可用的性质。 2.本题使用递…

初识MySQL · 库的操作

目录 前言&#xff1a; 增 有关编码 删 查 改 前言&#xff1a; 由前文可得&#xff0c;MySQL是目前主流的数据库&#xff0c;mysql是客户端&#xff0c;mysqld是一种网络服务&#xff0c;mysqld是一种数据库服务&#xff0c;而对于数据库来说&#xff0c;是一种存储数据…

Idea创建JDK17的maven项目失败

Idea创建JDK17的maven项目失败 Error occurred during initialization of VM Could not find agent library instrument on the library path, with error: Can’t find dependent libraries Possible solution: Check your maven runner VM options. Open Maven Runner setti…

Go-知识 模板

Go-知识 模板 1. 介绍2. Text/template 包3. Html/template 包4. 模板语法4.1 模板标签4.2 添加注释4.3 访问变量4.4 访问方法4.5 模板变量4.6 访问函数4.7 数据渲染4.8 条件判断4.9 循环遍历4.10 嵌入子模板4.11 局部变量4.12 输出字符串4.13 预定义的全局函数4.14 比较函数 1…

优化租赁小程序提升服务效率与用户体验的策略与实践

内容概要 在这个快速发展的商业环境中&#xff0c;租赁小程序成为了提升服务效率和用户体验的重要工具。通过对用户需求的深入挖掘&#xff0c;我们发现他们对于功能的便捷性、响应速度和界面的友好性有着极高的期待。因此&#xff0c;针对这些需求&#xff0c;完善租赁小程序…

基础数据结构--二叉树

一、二叉树的定义 二叉树是 n( n > 0 ) 个结点组成的有限集合&#xff0c;这个集合要么是空集&#xff08;当 n 等于 0 时&#xff09;&#xff0c;要么是由一个根结点和两棵互不相交的二叉树组成。其中这两棵互不相交的二叉树被称为根结点的左子树和右子树。 如图所示&am…

shell学习变量(二)

这里写目录标题 一、概念1、环境变量2、本地变量3、系统变量 二、环境变量三、本地变量四、系统变量五、定义变量规则1、命名规则2、定义方式3、unset命令&#xff1a;删除变量 一、概念 1、环境变量 环境变量指的是再当前进程有效&#xff0c;并且能够被子进程调用&#xff…

自动驾驶3D目标检测综述(六)

停更了好久终于回来了&#xff08;其实是因为博主去备考期末了hh&#xff09; 这一篇接着&#xff08;五&#xff09;的第七章开始讲述第八章的内容。第八章主要介绍的是三维目标检测的高效标签。 目录 第八章 三维目标检测高效标签 一、域适应 &#xff08;一&#xff09;…

如何恢复永久删除的PPT文件?查看数据恢复教程!

可以恢复永久删除的PPT文件吗&#xff1f; Microsoft PowerPoint应用程序是一种应用广泛的演示程序&#xff0c;在人们的日常生活中经常使用。商人、官员、学生等在学习和工作中会使用PowerPoint做报告和演示。PowerPoint在人们的学习和工作生活中占主导地位&#xff0c;每天都…