You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

物理设备删除文件后仍触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 19:52:57