Android 11/12无法从外部存储加载PDF至ListView问题求助
Android 11/12无法加载外部存储PDF文件的解决方案
问题核心原因
- Scoped Storage权限限制:Android 11(API 30)及以上强制启用Scoped Storage,
requestLegacyExternalStorage="true"仅对Android 10(API 29)生效,API 30+不再允许直接通过Environment.getExternalStorageDirectory()遍历整个外部存储目录。 - 缺失动态权限申请:
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
相关产品推荐
相关产品推荐

