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

Android本地PDF文件浏览选择应用的权限配置与代码修改咨询

Android本地PDF文件浏览选择应用的权限配置与代码修改咨询

我来帮你梳理下需要调整的几个关键部分,让你的应用能满足SDK21+的本地PDF浏览需求:

1. 调整SDK版本配置

你当前的minSdkVersion设为32,但需求是支持SDK21及以上,首先要修改Module级别的build.gradle配置:

android {
    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 33
        compileSdkVersion 33
        // 保留其他原有配置
    }
}

Dexter 6.2.3版本本身支持SDK21+,所以不需要更换依赖。

2. 适配跨版本存储权限与文件获取逻辑

你的当前代码用Environment.getExternalStorageDirectory()遍历文件,这在Android 10(SDK29)及以上会因为Scoped Storage限制无法访问所有目录,得针对不同版本做适配,这里推荐两种方案:

方案一:用MediaStore查询PDF(推荐,符合Google规范)

这种方式不需要申请特殊权限,适配所有SDK版本,替换你原来的findPdf方法:

private ArrayList<PdfItem> getPdfFilesFromMediaStore() {
    ArrayList<PdfItem> pdfList = new ArrayList<>();
    String[] projection = {
            MediaStore.Files.FileColumns._ID,
            MediaStore.Files.FileColumns.DISPLAY_NAME,
            MediaStore.Files.FileColumns.SIZE,
            MediaStore.Files.FileColumns.CONTENT_URI
    };

    // 筛选PDF类型文件
    String selection = MediaStore.Files.FileColumns.MIME_TYPE + " = ?";
    String[] selectionArgs = {"application/pdf"};

    // 按修改时间倒序排列
    String sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED + " DESC";

    Cursor cursor = getContentResolver().query(
            MediaStore.Files.getContentUri("external"),
            projection,
            selection,
            selectionArgs,
            sortOrder
    );

    if (cursor != null) {
        while (cursor.moveToNext()) {
            String fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME));
            long fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE));
            Uri fileUri = Uri.parse(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.CONTENT_URI)));
            
            // 用自定义类存储PDF信息,避免直接用File对象(Android10+可能无法访问真实路径)
            pdfList.add(new PdfItem(fileName, fileSize, fileUri));
        }
        cursor.close();
    }
    return pdfList;
}

需要创建一个简单的PdfItem类来存储文件信息:

public class PdfItem {
    private String fileName;
    private long fileSize;
    private Uri fileUri;

    public PdfItem(String fileName, long fileSize, Uri fileUri) {
        this.fileName = fileName;
        this.fileSize = fileSize;
        this.fileUri = fileUri;
    }

    // 生成getter方法
    public String getFileName() { return fileName; }
    public long getFileSize() { return fileSize; }
    public Uri getFileUri() { return fileUri; }
}

然后修改displayPdf方法,替换原来的文件遍历逻辑:

private void displayPdf() {
    recyclerView = findViewById(R.id.recycler_view);
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
    
    // 替换为自定义的PdfItem列表
    List<PdfItem> pdfItemList = getPdfFilesFromMediaStore();
    pdfAdapter = new PdfAdapter(this, pdfItemList);
    recyclerView.setAdapter(pdfAdapter);
}

方案二:申请MANAGE_EXTERNAL_STORAGE权限(仅用于需要遍历所有目录的场景)

如果必须遍历所有存储目录,需要在Manifest中添加特殊权限:

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

然后修改权限申请逻辑,当SDK>=30时,引导用户到系统设置开启权限:

private void runtimePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        if (!Environment.isExternalStorageManager()) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
            Uri uri = Uri.fromParts("package", getPackageName(), null);
            intent.setData(uri);
            startActivityForResult(intent, 100);
        } else {
            displayPdf();
        }
    } else {
        Dexter.withContext(MainActivity.this).withPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
                .withListener(new PermissionListener() {
                    @Override
                    public void onPermissionGranted(PermissionGrantedResponse permissionGrantedResponse) {
                        displayPdf();
                    }

                    @Override
                    public void onPermissionDenied(PermissionDeniedResponse permissionDeniedResponse) {
                        Toast.makeText(MainActivity.this, "Permission is Required", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(PermissionRequest permissionRequest, PermissionToken permissionToken) {
                        permissionToken.continuePermissionRequest();
                    }
                }).check();
    }
}

// 处理权限申请结果
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 100) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (Environment.isExternalStorageManager()) {
                displayPdf();
            } else {
                Toast.makeText(this, "Permission is Required", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

同时要修复原findPdf方法的空指针问题(避免目录不存在或无权限时崩溃):

public ArrayList<File> findPdf (File file) {
    ArrayList<File> arrayList = new ArrayList<>();
    // 先判断目录是否有效
    if (file == null || !file.exists() || !file.isDirectory()) {
        return arrayList;
    }
    File[] files = file.listFiles();
    // 避免listFiles返回null
    if (files == null) {
        return arrayList;
    }
    for (File singleFile: files) {
        if (singleFile.isDirectory()) {
            // 去掉isHidden判断,避免漏掉隐藏目录里的PDF
            arrayList.addAll(findPdf(singleFile));
        } else {
            // 忽略大小写,匹配.PDF后缀
            if (singleFile.getName().toLowerCase().endsWith(".pdf")){
                arrayList.add(singleFile);
            }
        }
    }
    return arrayList;
}

3. 添加PDF阅读功能

当前代码只实现了文件列表,还需要添加点击打开PDF的逻辑:

适配MediaStore Uri的打开方式(推荐)

在PdfAdapter的onBindViewHolder中添加点击事件:

@Override
public void onBindViewHolder(@NonNull PdfViewHolder holder, int position) {
    PdfItem pdfItem = pdfItemList.get(position);
    holder.tvFileName.setText(pdfItem.getFileName());
    
    holder.itemView.setOnClickListener(v -> {
        openPdfFile(pdfItem.getFileUri());
    });
}

private void openPdfFile(Uri pdfUri) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(pdfUri, "application/pdf");
    // 授予临时读取权限
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    try {
        context.startActivity(intent);
    } catch (ActivityNotFoundException e) {
        Toast.makeText(context, "未找到可打开PDF的应用", Toast.LENGTH_SHORT).show();
    }
}

适配File对象的打开方式(需要FileProvider)

如果用方案二的File列表,需要配置FileProvider来兼容Android7+:

  1. 在Manifest中添加Provider配置:
<application>
    <!-- 其他配置 -->
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
</application>
  1. 在res/xml目录下创建file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>
  1. 修改打开PDF的方法:
private void openPdfFile(File pdfFile) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    Uri uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", pdfFile);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    } else {
        uri = Uri.fromFile(pdfFile);
    }
    intent.setDataAndType(uri, "application/pdf");
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    try {
        context.startActivity(intent);
    } catch (ActivityNotFoundException e) {
        Toast.makeText(context, "未找到可打开PDF的应用", Toast.LENGTH_SHORT).show();
    }
}

4. 其他细节优化

  • 给RecyclerView的Item添加文件名、文件大小的显示,提升用户体验。
  • 处理空列表的情况:当没有找到PDF时,显示提示文本。
  • 可以添加下拉刷新功能,让用户重新扫描PDF文件。

备注:内容来源于stack exchange,提问作者Marfog509

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 12:48:03