物理设备删除文件后仍触发EEXIST错误的解决方案咨询
问题分析与解决方案
问题根源
你遇到的是Android系统中文件系统与MediaStore索引不同步的问题:用户通过文件管理器删除zip后,MediaStore的媒体数据库可能未及时更新,仍保留该文件的条目;当你再次用同名创建时,系统通过MediaStore检查到“文件已存在”,从而抛出错误。
最佳处理方案
方案1:创建前主动清理残留(兼容所有Android版本)
在生成zip包之前,先执行两步清理操作,确保物理文件和MediaStore条目都不存在:
- 检查目标路径的物理文件是否存在,存在则直接删除
- 查询MediaStore中是否有同名文件条目,存在则删除对应数据库记录
代码示例:
private fun prepareZipFile(zipFileName: String): File? { val zipFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), zipFileName) // 1. 删除物理文件(如果存在) if (zipFile.exists()) { zipFile.delete() } // 2. 删除MediaStore中残留的条目 val contentResolver = requireContext().contentResolver val queryUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI val selection = "${MediaStore.Downloads.DISPLAY_NAME} = ?" val selectionArgs = arrayOf(zipFileName) val cursor = contentResolver.query(queryUri, arrayOf(MediaStore.Downloads._ID), selection, selectionArgs, null) cursor?.use { if (it.moveToFirst()) { val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Downloads._ID)) val deleteUri = ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id) contentResolver.delete(deleteUri, null, null) } } return zipFile.takeIf { !it.exists() } }
修改原zipDocs()方法,先调用清理逻辑:
private fun zipDocs(){ val zipFileName = "${Fragment.name} ${Fragment.report} $date.zip" val zipFile = prepareZipFile(zipFileName) ?: run { Toast.makeText(requireContext(), "无法清理残留文件", Toast.LENGTH_SHORT).show() return } val outDest = FileOutputStream(zipFile) val out = ZipOutputStream(outDest) // 后续PDF、图片打包逻辑保持不变... }
方案2:改用MediaStore API创建文件(Android 10+推荐)
从Android 10(API 29)开始,系统限制了直接访问公共目录的权限,推荐使用MediaStore API创建/管理文件,这样系统会自动同步MediaStore索引,从根源避免不同步问题:
private fun createZipViaMediaStore(zipFileName: String): OutputStream? { val contentResolver = requireContext().contentResolver val values = ContentValues().apply { put(MediaStore.Downloads.DISPLAY_NAME, zipFileName) put(MediaStore.Downloads.MIME_TYPE, "application/zip") put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) } // 先删除已有同名条目 val deleteUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI contentResolver.delete(deleteUri, "${MediaStore.Downloads.DISPLAY_NAME} = ?", arrayOf(zipFileName)) // 创建新文件并获取输出流 val uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values) return uri?.let { contentResolver.openOutputStream(it) } }
替换原zipDocs()中的输出流创建逻辑:
private fun zipDocs(){ val zipFileName = "${Fragment.name} ${Fragment.report} $date.zip" val outStream = createZipViaMediaStore(zipFileName) ?: run { Toast.makeText(requireContext(), "无法创建zip文件", Toast.LENGTH_SHORT).show() return } val out = ZipOutputStream(outStream) // 后续PDF、图片打包逻辑保持不变... }
额外注意事项
- Android 13+需申请
READ_MEDIA_IMAGES和READ_MEDIA_DOCUMENTS权限,才能读取图片和PDF文件 - 若使用方案2,打包完成后可调用
contentResolver.notifyChange(uri, null),主动通知系统更新MediaStore索引 - 避免硬编码路径拼接,改用
File(parentDir, fileName)的方式减少路径错误
内容的提问来源于stack exchange,提问作者Whitt
相关产品推荐
相关产品推荐

