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

Android 11+重命名同名文件后出现FileNotFoundException权限异常

Android 11+ 文件替换后权限拒绝问题解决方案

问题本质

Android 11+ 引入的存储沙箱机制中,应用通过路径直接访问外部存储文件时,系统会基于文件的MediaStore记录或应用自身的文件创建关联授予权限。当外部文件管理器替换同名文件(如测试场景中把data2.aux改回data.aux),新文件的inode与旧文件完全不同,应用之前的权限关联仅针对旧文件,导致系统判定应用无权限访问新文件,触发EACCES异常。

你之前用MediaScanner解决删除重建问题的原因是:扫描后系统移除了旧文件的MediaStore记录,应用重新创建文件时会生成新的记录并建立权限关联;但文件替换场景下,旧记录仍指向原文件inode,新文件未被应用“认领”,因此扫描无效。

合规解决方案(无需MANAGE_EXTERNAL_STORAGE)

方案1:采用存储访问框架(SAF)获取持久化目录权限

这是谷歌推荐的合规方案,完全适配Android 11+存储政策,能彻底解决文件替换后的权限问题。核心思路是让用户主动授权应用访问目标目录,应用通过SAF API而非路径直接操作文件。

步骤1:请求用户授权目标目录

private static final int REQUEST_CODE_PICK_DIR = 1001;

// 触发目录选择界面
private void requestWorkDirPermission() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    // 可选:引导用户直接进入Documents目录
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, REQUEST_CODE_PICK_DIR);
}

步骤2:处理授权结果并持久化权限

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_PICK_DIR && resultCode == RESULT_OK) {
        Uri treeUri = data.getData();
        // 申请持久化读写权限,避免应用重启后权限失效
        getContentResolver().takePersistableUriPermission(treeUri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // 将Uri保存到SharedPreferences,供后续启动使用
        SharedPreferences prefs = getSharedPreferences("AppStoragePrefs", MODE_PRIVATE);
        prefs.edit().putString("target_work_dir_uri", treeUri.toString()).apply();
    }
}

步骤3:通过SAF读写data.aux文件

private void readDataAuxFile() {
    SharedPreferences prefs = getSharedPreferences("AppStoragePrefs", MODE_PRIVATE);
    String treeUriStr = prefs.getString("target_work_dir_uri", null);
    if (treeUriStr == null) {
        // 未授权,引导用户重新授权
        requestWorkDirPermission();
        return;
    }

    Uri treeUri = Uri.parse(treeUriStr);
    DocumentFile rootDir = DocumentFile.fromTreeUri(this, treeUri);
    if (rootDir == null || !rootDir.exists()) {
        // 目录不存在,提示用户
        return;
    }

    // 查找目标文件
    DocumentFile dataFile = rootDir.findFile("data.aux");
    if (dataFile == null || !dataFile.exists()) {
        // 文件不存在,创建新文件
        dataFile = rootDir.createFile("application/octet-stream", "data.aux");
    }

    // 读取文件内容
    try (InputStream is = getContentResolver().openInputStream(dataFile.getUri())) {
        DataInputStream dis = new DataInputStream(is);
        // 处理文件内容逻辑
        // ...
    } catch (IOException e) {
        System.err.println("读取文件异常: " + e.getMessage());
    }
}

方案2:访问失败时通过ContentResolver重试(临时兼容方案)

如果不想立刻切换到SAF,可在FileNotFoundException触发时,通过ContentResolver查询文件的Uri,再通过Uri打开输入流:

try {
    DataInputStream fis = new DataInputStream(new FileInputStream(dataFile));
    // 正常处理逻辑
} catch (FileNotFoundException e) {
    // 尝试通过ContentResolver查询文件
    Uri fileUri = MediaStore.Files.getContentUri("external");
    String[] projection = {MediaStore.Files.FileColumns._ID};
    String selection = MediaStore.Files.FileColumns.DATA + " = ?";
    String[] selectionArgs = {dataFile.getAbsolutePath()};
    
    Cursor cursor = getContentResolver().query(fileUri, projection, selection, selectionArgs, null);
    if (cursor != null && cursor.moveToFirst()) {
        long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
        Uri targetUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id);
        try (InputStream is = getContentResolver().openInputStream(targetUri)) {
            DataInputStream dis = new DataInputStream(is);
            // 重新处理文件内容
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        cursor.close();
    }
}

注意:此方案依赖MediaStore索引的及时性,若文件刚被替换还未完成索引,仍可能失败,仅作为临时过渡方案。

关键说明

  • 放弃路径直接访问:Android 11+后,外部存储的路径访问权限仅对应用自身创建的文件有效,外部修改的文件必须通过SAF或ContentResolver访问,这是合规的核心要求。
  • SAF的优势:用户明确授权目录,符合隐私政策,不受文件重命名、替换操作影响,无需申请特殊权限,完全满足跨设备复制文件的需求(用户只需在新设备上授权相同目录,复制文件后即可访问)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:42:37