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

Android SDK 30及以上版本如何访问外部存储中的任意文件?

Android 30+ 读取外部存储中非自建文件的解决方案

针对你在Android 30+版本中无法访问Documents/MyApp目录下非自建文件的问题,目前合规且可行的核心方案是通过SAF(存储访问框架)获取整个目录的访问权限,以下是具体修正和说明:

1. 修正SAF的使用方式(关键)

你之前使用ACTION_OPEN_DOCUMENT仅能选择单个文件,且初始Uri用Uri.fromFile()的方式在Scoped Storage下会被系统限制,导致无法看到非自建文件。正确做法是使用ACTION_OPEN_DOCUMENT_TREE让用户授权整个Documents/MyApp目录的访问权限,这样应用就能访问该目录下所有文件(包括用户上传的)。

示例代码:

private static final int REQUEST_CODE_PICK_DIR = 1001;

// 唤起目录选择器,初始定位到Documents/MyApp
private void requestMyAppDirAccess() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    
    // 尝试设置初始目录为Documents/MyApp
    Uri externalFilesUri = MediaStore.Files.getContentUri("external");
    String selection = MediaStore.Files.FileColumns.RELATIVE_PATH + "=?";
    String[] selectionArgs = new String[]{"Documents/MyApp/"};
    
    Cursor cursor = getContentResolver().query(externalFilesUri, null, selection, selectionArgs, null);
    if (cursor != null && cursor.moveToFirst()) {
        long docId = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
        Uri initialDirUri = DocumentsContract.buildDocumentUriUsingTree(externalFilesUri, String.valueOf(docId));
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialDirUri);
        cursor.close();
    }
    
    startActivityForResult(intent, REQUEST_CODE_PICK_DIR);
}

// 处理权限回调
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_PICK_DIR && resultCode == RESULT_OK && data != null) {
        Uri treeUri = data.getData();
        if (treeUri != null) {
            // 持久化权限,重启应用后仍有效
            getContentResolver().takePersistableUriPermission(treeUri,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            
            // 遍历目录下的所有文件(包括非自建)
            traverseDirectory(treeUri);
        }
    }
}

// 遍历SAF授权的目录
private void traverseDirectory(Uri treeUri) {
    Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri,
            DocumentsContract.getTreeDocumentId(treeUri));
    
    Cursor cursor = getContentResolver().query(childrenUri,
            new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_DOCUMENT_ID},
            null, null, null);
    
    if (cursor != null) {
        while (cursor.moveToNext()) {
            String fileName = cursor.getString(0);
            Log.d("FileAccess", "找到文件:" + fileName);
            // 根据documentId获取文件Uri,进行读取操作
            Uri fileUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, cursor.getString(1));
            // 读取文件示例
            try (InputStream is = getContentResolver().openInputStream(fileUri)) {
                // 处理文件流
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        cursor.close();
    }
}

2. 为什么之前的方案无效?

  • EXTERNAL_STORAGE权限:Android 30+中该权限仅能让应用访问自己创建的文件,无法突破Scoped Storage对非自建文件的限制。
  • READ_MEDIA权限:仅针对图片、音频、视频类媒体文件,你的自定义文档不在其覆盖范围内,所以无法生效。
  • MANAGE_EXTERNAL_STORAGE权限:Google Play对该权限的审核极为严格,仅允许系统级或特殊用途应用使用,普通应用提交会被拒绝。
  • 旧SAF代码问题:ACTION_OPEN_DOCUMENT仅能授权单个文件,且Uri.fromFile()生成的file:// Uri在Scoped Storage下被系统屏蔽,导致选择器无法显示非自建文件。

3. 关于intent-filter的补充

你之前注册的file scheme intent-filter在Android 10+已被限制,若要让其他应用唤起你的应用打开.custom文件,需改用content scheme:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="content" />
    <data android:mimeType="application/octet-stream" />
    <data android:pathPattern=".*\\.custom" />
</intent-filter>

但此方案仅解决外部应用唤起的场景,无法让你的应用主动访问目录下的非自建文件,核心需求仍需依赖SAF目录授权。

总结

在Android 30+的Scoped Storage规则下,SAF目录授权是唯一合规且可行的方案,无需移除功能。按照上述代码实现后,用户只需授权一次Documents/MyApp目录,应用就能正常访问其中所有文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 09:22:20