DataStorePreferences写入速度问题:快速关机致数据损坏丢失求解决方案
解决DataStore快速关机后文件损坏及数据丢失问题
问题根源
DataStore Preferences基于ProtoBuf序列化数据,edit操作是异步执行的——修改先在内存中完成,后台再异步写入磁盘。如果在写入过程中快速关机/断电,会导致ProtoBuf文件处于半写入状态,下次读取时触发CorruptionException。当前使用的ReplaceFileCorruptionHandler直接返回空偏好,导致所有设置丢失。
解决方案
1. 等待写入完成后再触发关机(自助终端场景最优)
因为是kiosk应用,通常可以控制关机流程。将保存操作改为挂起函数,确保写入完成后再执行关机逻辑:
// 改为挂起函数,确保写入完成后才返回 private suspend fun saveString(key: String, value: String) { val dataStoreKey = stringPreferencesKey(key) context.dataStore.edit { settings -> settings[dataStoreKey] = value } } // 调用示例(在协程中执行) lifecycleScope.launch { saveString("device_id", "12345") // 等待写入完成后,再执行关机操作 performShutdown() }
如果是系统触发的关机,注册ACTION_SHUTDOWN广播接收器,在接收器中等待所有待完成的DataStore写入任务:
// 全局保存待完成的写入任务 private val pendingWrites = mutableListOf<Deferred<Unit>>() // 修改保存函数,返回Deferred以便追踪 private fun saveString(key: String, value: String): Deferred<Unit> { return CoroutineScope(Dispatchers.IO).async { val dataStoreKey = stringPreferencesKey(key) context.dataStore.edit { settings -> settings[dataStoreKey] = value } }.also { pendingWrites.add(it) } } // 广播接收器中等待所有任务完成 class ShutdownReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == Intent.ACTION_SHUTDOWN) { runBlocking { pendingWrites.forEach { it.await() } } } } }
2. 自定义CorruptionHandler,从备份恢复数据
不要直接清空数据,而是在损坏时尝试从备份文件恢复:
// 自定义损坏处理器 private val customCorruptionHandler = object : CorruptionHandler<Preferences> { override suspend fun handleCorruption(exceptions: List<Throwable>): Preferences { val dataStoreDir = File(context.filesDir, "datastore") val targetFile = File(dataStoreDir, "commonSettings.preferences_pb") val backupFile = File(dataStoreDir, "commonSettings.preferences_pb.bak") return try { // 优先尝试从备份恢复 if (backupFile.exists()) { backupFile.copyTo(targetFile, overwrite = true) PreferencesSerializer.readFrom(FileInputStream(targetFile)) } else { emptyPreferences() } } catch (e: Exception) { Log.e("CorruptionHandler", "Backup restore failed", e) emptyPreferences() } } } // 初始化DataStore时配置自定义处理器 private val Context.dataStore: DataStore<Preferences> by preferencesDataStore( name = "commonSettings", corruptionHandler = customCorruptionHandler )
同时,在每次成功写入后创建备份:
private suspend fun saveStringWithBackup(key: String, value: String) { val dataStoreKey = stringPreferencesKey(key) context.dataStore.edit { settings -> settings[dataStoreKey] = value } // 写入完成后备份文件 val dataStoreDir = File(context.filesDir, "datastore") val targetFile = File(dataStoreDir, "commonSettings.preferences_pb") val backupFile = File(dataStoreDir, "commonSettings.preferences_pb.bak") targetFile.copyTo(backupFile, overwrite = true) }
3. 强制写入磁盘同步
在edit完成后,手动触发文件同步,减少缓冲导致的写入延迟:
private suspend fun saveStringAndSync(key: String, value: String) { val dataStoreKey = stringPreferencesKey(key) context.dataStore.edit { settings -> settings[dataStoreKey] = value } // 强制同步到磁盘 val dataStoreFile = File(context.filesDir, "datastore/commonSettings.preferences_pb") FileChannel.open(dataStoreFile.toPath(), StandardOpenOption.WRITE).use { channel -> channel.force(true) // true表示同步元数据 } }
注意:此操作会增加写入耗时,适合写入频率低的kiosk场景。
总结
针对自助终端的特性,优先采用等待写入完成再关机的方案,从根源避免文件损坏;同时配合备份+自定义CorruptionHandler作为兜底,即使出现损坏也能恢复数据,避免全部丢失。
内容的提问来源于stack exchange,提问作者Lighter
相关产品推荐
相关产品推荐

