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

Android 11分区存储无需MANAGE_EXTERNAL_STORAGE权限获取全部PDF文件

Android11分区存储下无需MANAGE_EXTERNAL_STORAGE获取PDF文件解决方案

问题核心原因

你遇到的问题是Android11分区存储权限限制和旧代码API使用不当共同导致的:

  1. Android11+没有MANAGE_EXTERNAL_STORAGE权限时,MediaStore.Files表默认仅返回当前应用创建的公共存储文件,其他应用生成的PDF无法通过该接口查询
  2. MediaStore.Files.FileColumns.DATA列在Android11后已废弃,第三方应用无权限直接访问该路径,new File(path).exists()永远返回false
  3. 部分PDF文件的MIME_TYPE未被系统正确识别,仅用MIME_TYPE筛选会漏检文件

前置配置

  • AndroidManifest.xml权限配置
<!-- Android 6~12需要读取存储权限,Android13+该权限仅能访问媒体文件,非媒体PDF需用SAF方案 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<application
    ...
    <!-- 仅Android10生效,Android11自动忽略 -->
    android:requestLegacyExternalStorage="true"
    ...>
  • 先动态申请READ_EXTERNAL_STORAGE权限(Android6~12),权限未授予时查询MediaStore会返回空游标

方案1:仅查询当前应用创建的PDF(无需用户手动授权目录)

修改后的代码直接返回可直接访问的文件Uri,不要使用本地路径:

protected ArrayList<Uri> getPdfList() {
    ArrayList<Uri> pdfList = new ArrayList<>();
    Uri collection;

    final String[] projection = new String[]{
            MediaStore.Files.FileColumns._ID,
            MediaStore.Files.FileColumns.DISPLAY_NAME,
            MediaStore.Files.FileColumns.DATE_ADDED,
            MediaStore.Files.FileColumns.MIME_TYPE,
    };

    final String sortOrder = MediaStore.Files.FileColumns.DATE_ADDED + " DESC";
    // 同时筛选MIME_TYPE和文件名后缀,避免漏检
    final String selection = MediaStore.Files.FileColumns.MIME_TYPE + " = ? OR "
            + MediaStore.Files.FileColumns.DISPLAY_NAME + " LIKE '%.pdf'";

    final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("pdf");
    final String[] selectionArgs = new String[]{mimeType};

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        collection = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
    } else {
        collection = MediaStore.Files.getContentUri("external");
    }

    try (Cursor cursor = getContentResolver().query(collection, projection, selection, selectionArgs, sortOrder)) {
        if (cursor != null && cursor.moveToFirst()) {
            int columnId = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID);
            do {
                // 拼接可直接访问的Content Uri
                Uri pdfUri = ContentUris.withAppendedId(collection, cursor.getLong(columnId));
                pdfList.add(pdfUri);
            } while (cursor.moveToNext());
        }
    } catch (SecurityException e) {
        e.printStackTrace();
    }
    return pdfList;
}

拿到Uri后可通过getContentResolver().openInputStream(uri)读取文件内容。


方案2:获取全量PDF(无需MANAGE_EXTERNAL_STORAGE权限)

如果需要拿到手机里所有PDF文件,用存储访问框架(SAF)引导用户授权存储目录即可,无需特殊权限:

  1. 唤起SAF让用户选择存储根目录授权
private static final int REQUEST_SAF_CODE = 1001;

private void requestStorageAccess() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    intent.addFlags(
        Intent.FLAG_GRANT_READ_URI_PERMISSION
        | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION // 持久化授权,APP重启后不需要重新申请
        | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
    );
    startActivityForResult(intent, REQUEST_SAF_CODE);
}
  1. 授权成功后遍历目录下所有PDF
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_SAF_CODE && resultCode == RESULT_OK && data != null) {
        Uri treeUri = data.getData();
        // 保存持久化授权
        getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        ArrayList<Uri> allPdfList = traversePdfFromTree(treeUri);
    }
}

private ArrayList<Uri> traversePdfFromTree(Uri treeUri) {
    ArrayList<Uri> pdfList = new ArrayList<>();
    DocumentFile root = DocumentFile.fromTreeUri(this, treeUri);
    if (root == null || !root.exists()) return pdfList;
    Queue<DocumentFile> dirQueue = new LinkedList<>();
    dirQueue.add(root);
    while (!dirQueue.isEmpty()) {
        DocumentFile current = dirQueue.poll();
        if (current.isDirectory()) {
            DocumentFile[] children = current.listFiles();
            for (DocumentFile child : children) {
                dirQueue.add(child);
            }
        } else {
            String fileName = current.getName();
            if (fileName != null && fileName.toLowerCase().endsWith(".pdf")) {
                pdfList.add(current.getUri());
            }
        }
    }
    return pdfList;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 12:15:03