Android Worker类完成数据库恢复后无法还原ZIP中的媒体文件
解决方案:先恢复Room数据库再处理媒体文件
核心问题在于你当前的单次ZIP遍历逻辑无法保证先处理数据库再处理所有媒体文件——ZIP条目是按压缩时的顺序存储的,没法在单次遍历中跳过媒体条目先处理数据库,之后再回头处理媒体。下面提供两种可靠的实现思路:
思路1:两次遍历ZIP文件
第一次遍历专门处理数据库文件,第二次遍历处理所有媒体文件。这种方式逻辑简单,适合ZIP文件体积不大的场景:
private fun restoreDataFromZip(context: Context, zipFileUri: Uri): Boolean { val contentResolver = context.contentResolver var dbRestorationSuccess = false // 第一次遍历:仅处理数据库 contentResolver.openInputStream(zipFileUri)?.use { inputStream -> ZipInputStream(inputStream).use { zipInputStream -> var entry: ZipEntry? while (zipInputStream.nextEntry.also { entry = it } != null) { val entryName = entry?.name ?: continue if (entryName == "databases/GazeApp.db") { try { restoreDatabaseFile(context, zipInputStream) dbRestorationSuccess = true } catch (e: Exception) { Log.e("DatabaseRestoreWorker", "Failed to restore database", e) return false } finally { zipInputStream.closeEntry() } break // 找到数据库处理完就跳出第一次遍历 } zipInputStream.closeEntry() // 跳过其他条目 } } } if (!dbRestorationSuccess) return false // 第二次遍历:处理所有媒体文件 contentResolver.openInputStream(zipFileUri)?.use { inputStream -> ZipInputStream(inputStream).use { zipInputStream -> var entry: ZipEntry? while (zipInputStream.nextEntry.also { entry = it } != null) { val entryName = entry?.name ?: continue if (entryName.startsWith("contacts/")) { try { restoreContactsFile(context, entryName, zipInputStream) } catch (e: Exception) { Log.e("DatabaseRestoreWorker", "Failed to restore media file: $entryName", e) // 可根据需求决定是否终止整个流程 } finally { zipInputStream.closeEntry() } } else { zipInputStream.closeEntry() // 跳过非媒体条目 } } } } return true }
思路2:单次遍历缓存媒体条目内容(适合大ZIP文件)
如果ZIP文件体积较大,不想重复打开流,可以在单次遍历中先缓存媒体条目的内容,等数据库处理完成后再处理缓存的媒体:
private fun restoreDataFromZip(context: Context, zipFileUri: Uri): Boolean { val contentResolver = context.contentResolver var dbRestorationSuccess = false val mediaEntries = mutableListOf<Pair<String, ByteArray>>() // 缓存媒体条目名称和内容 contentResolver.openInputStream(zipFileUri)?.use { inputStream -> ZipInputStream(inputStream).use { zipInputStream -> var entry: ZipEntry? while (zipInputStream.nextEntry.also { entry = it } != null) { val entryName = entry?.name ?: continue if (entryName == "databases/GazeApp.db") { try { restoreDatabaseFile(context, zipInputStream) dbRestorationSuccess = true } catch (e: Exception) { Log.e("DatabaseRestoreWorker", "Failed to restore database", e) return false } finally { zipInputStream.closeEntry() } } else if (entryName.startsWith("contacts/")) { // 缓存媒体文件内容 val content = zipInputStream.readBytes() mediaEntries.add(entryName to content) zipInputStream.closeEntry() } else { zipInputStream.closeEntry() } } } } if (!dbRestorationSuccess) return false // 处理缓存的媒体文件 mediaEntries.forEach { (entryName, content) -> try { val outputFile = File(context.filesDir, entryName) outputFile.parentFile?.mkdirs() outputFile.writeBytes(content) } catch (e: Exception) { Log.e("DatabaseRestoreWorker", "Failed to restore media file: $entryName", e) } } return true }
关键注意事项
- 数据库连接重置:确保数据库恢复完成后,调用
GazeDatabase.resetDatabase()或重新获取数据库实例,避免旧连接导致的读写异常。 - 异常处理策略:媒体文件恢复失败时,根据业务需求决定是否终止整个流程——如果媒体不是核心数据,可以仅记录日志继续执行。
- 流的正确关闭:无论是否处理当前条目,都必须调用
zipInputStream.closeEntry(),否则会破坏流的状态,导致后续条目读取失败。
内容的提问来源于stack exchange,提问作者Dominik
相关产品推荐
相关产品推荐

