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+:
- 在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>
- 在
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>
- 修改打开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
相关产品推荐
相关产品推荐

