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

Android 10系统删除文件/文件夹后无法使用相同名称创建新文件问题

问题根因

Android 10及以上版本引入的MediaStore机制存在以下特性,是导致你问题的核心原因:

  • 手动删除文件/文件夹后,MediaStore数据库不会立即清除对应记录,只会标记为IS_TRASHED=1(回收站状态)或残留未清理的脏数据,此时插入同名文件会因为唯一索引冲突返回null
  • 你当前使用MediaStore.MediaColumns.DATA列查询记录的方式已被Android官方废弃,Android 11之后该列仅返回无效空值,导致你无法查询到残留的脏记录,误以为路径无占用
修复方案

1. 修复文件存在性查询逻辑

废弃DATA列,改用RELATIVE_PATH + DISPLAY_NAME作为查询条件,同时开启回收站记录查询权限,确保能查到所有残留的脏记录:

public static Uri isFileExist(String relativePath, String fileName) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
        // 低于Android 10版本走原有逻辑即可
        return null;
    }
    Uri queryUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI.buildUpon()
            // 允许查询回收站中的记录
            .appendQueryParameter(MediaStore.QUERY_ARG_MATCH_TRASHED, "true")
            .build();
    String[] projection = new String[]{MediaStore.MediaColumns._ID};
    String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=? AND " 
            + MediaStore.MediaColumns.DISPLAY_NAME + "=?";
    String[] selectionArgs = new String[]{relativePath, fileName};
    try (Cursor cursor = getContext().getContentResolver().query(queryUri, projection, selection, selectionArgs, null)) {
        if (cursor != null && cursor.moveToFirst()) {
            long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
            return ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id);
        }
    }
    return null;
}

2. 新增冲突后重试逻辑

插入返回null时,先删除残留的脏记录再重试插入:

public static void createFile() throws IOException {
    String contentType = "text/log";
    Date cDate = new Date(System.currentTimeMillis());
    String today = new SimpleDateFormat("yyyy_MM_dd").format(cDate);
    long seconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
    String relativePath = Environment.DIRECTORY_DOWNLOADS + File.separator + "MyApp Log" + File.separator;
    String fileName = today + ".txt";

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, contentType);
        contentValues.put(MediaStore.MediaColumns.DATE_ADDED, seconds);
        contentValues.put(MediaStore.MediaColumns.DATE_MODIFIED, seconds);
        contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
        contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath);

        ContentResolver resolver = getContext().getContentResolver();
        Uri outputUri = resolver.insert(getDownloadUri(), contentValues);

        // 插入失败时先删残留记录再重试
        if (outputUri == null) {
            Uri existUri = isFileExist(relativePath, fileName);
            if (existUri != null) {
                // 删除残留的脏记录
                resolver.delete(existUri, null, null);
                // 重试插入
                outputUri = resolver.insert(getDownloadUri(), contentValues);
            }
            if (outputUri == null) {
                throw new IOException("Failed to create new MediaStore record.");
            }
        }

        try (final OutputStream stream = resolver.openOutputStream(outputUri)) {
            if (stream == null)
                return;
            // 写入日志内容的逻辑写在这里
        } finally {
            ContentValues updateValues = new ContentValues();
            updateValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
            resolver.update(outputUri, updateValues, null, null);
        }
    }
}
额外注意点
  • RELATIVE_PATH末尾请加上File.separator,避免部分系统识别路径错误
  • 如果应用需要支持覆盖当天已存在的日志文件,可以在插入前主动查询是否存在记录,存在直接打开写入即可,无需反复创建新文件

内容的提问来源于stack exchange,提问作者propoLis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 12:15:00