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

Android 11/12无法从外部存储加载PDF至ListView问题求助

Android 11/12无法加载外部存储PDF文件的解决方案

问题核心原因

  1. Scoped Storage权限限制:Android 11(API 30)及以上强制启用Scoped Storage,requestLegacyExternalStorage="true"仅对Android 10(API 29)生效,API 30+不再允许直接通过Environment.getExternalStorageDirectory()遍历整个外部存储目录。
  2. 缺失动态权限申请:READ_EXTERNAL_STORAGE属于危险权限,Android 6.0+必须在运行时动态申请,仅在Manifest中声明无法获取有效权限。

分步解决方案

1. 更新Manifest权限配置

适配不同Android版本的权限要求:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_DOCUMENTS" android:minSdkVersion="33" />

<application
    ...
    <!-- 移除android:requestLegacyExternalStorage="true"(仅API29有效) -->
>

2. 添加动态权限申请逻辑

在MainActivity中实现权限申请与回调:

private static final int REQUEST_PERMISSION_CODE = 100;

// 检查权限是否已授权
private boolean checkPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_DOCUMENTS) == PackageManager.PERMISSION_GRANTED;
    } else {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
    }
}

// 发起权限申请
private void requestPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_MEDIA_DOCUMENTS}, REQUEST_PERMISSION_CODE);
    } else {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_CODE);
    }
}

// 权限申请回调处理
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_PERMISSION_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            displayPdf();
        } else {
            Toast.makeText(this, "需要权限才能访问PDF文件", Toast.LENGTH_SHORT).show();
        }
    }
}

3. 替换PDF查询逻辑(适配Scoped Storage)

放弃文件目录遍历,改用MediaStore查询外部存储中的PDF文件:

// 获取所有PDF文件的Uri列表
private ArrayList<Uri> getPdfUris() {
    ArrayList<Uri> pdfUris = new ArrayList<>();
    ContentResolver resolver = getContentResolver();

    // 查询条件:筛选MIME类型为PDF的文件
    String selection = MediaStore.Files.FileColumns.MIME_TYPE + " = ?";
    String[] selectionArgs = {"application/pdf"};
    String sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED + " DESC";

    Cursor cursor = resolver.query(
            MediaStore.Files.getContentUri("external"),
            new String[]{MediaStore.Files.FileColumns._ID},
            selection,
            selectionArgs,
            sortOrder
    );

    if (cursor != null) {
        while (cursor.moveToNext()) {
            long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
            Uri pdfUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id);
            pdfUris.add(pdfUri);
        }
        cursor.close();
    }
    return pdfUris;
}

// 更新displayPdf方法
public void displayPdf(){
    recyclerView = findViewById(R.id.rv);
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(new GridLayoutManager(this,3));
    ArrayList<Uri> pdfUris = getPdfUris();
    // 需同步修改MainAdapter,使其支持Uri类型的数据源
    adapter = new MainAdapter(this,pdfUris,this);
    recyclerView.setAdapter(adapter);
}

// 更新PDF打开逻辑
@Override
public void onPdfSelected(Uri pdfUri) {
    Intent intent = new Intent(MainActivity.this, PDFwatch.class);
    intent.setData(pdfUri);
    // 授予临时读取权限,确保PDFwatch能访问该Uri
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(intent);
}

4. 适配Adapter与PDFwatch页面

  • 修改MainAdapter:接收ArrayList<Uri>作为数据源,通过ContentResolver查询文件名等元信息。
  • 修改PDFwatch:通过Intent.getData()获取Uri,使用ContentResolver.openInputStream(uri)读取PDF内容进行展示。

可选扩展方案

如果需要访问用户自定义目录下的PDF,可以使用ACTION_OPEN_DOCUMENT_TREE让用户手动选择目录,再通过DocumentFile遍历该目录下的PDF文件,这种方式能访问更多目录,但需用户主动授权。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 17:20:36