Android文档浏览App权限异常与文件加载失败问题求助
问题分析与解决方案
核心问题点
- 权限请求为异步操作,原代码在调用
requestPermissions后立即执行加载逻辑,此时权限尚未完成授予,导致读取失败 onCreate中重复调用多套文档加载方法,后执行的方法会覆盖之前的documentList,最终显示空列表- Android 10+(API 29)中
MediaStore.Files.FileColumns.DATA字段已废弃,无法直接获取文件路径;且Uri.fromFile()在Android 7+会触发FileUriExposedException - 未适配Android 13+的细分媒体权限,导致高版本系统权限申请不完整
修正步骤
1. 调整权限请求逻辑,异步等待权限结果
移除onCreate中直接调用的加载方法,仅在权限授予后执行加载:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 适配Android 13+细分权限 String[] permissions; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { permissions = new String[]{Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_DOCUMENTS}; } else { permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; } ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE); } else { // 低版本直接加载文档 loadDocumentsWithMediaStore(); } }
2. 统一文档加载逻辑,适配Scoped Storage
废弃冗余的loadDocumentsWithScopedStorage方法,统一使用loadDocumentsWithMediaStore并修复高版本适配问题:
// 自定义Document类存储文件信息(替代直接使用File) public class Document { private String name; private Uri contentUri; public Document(String name, Uri contentUri) { this.name = name; this.contentUri = contentUri; } public String getName() { return name; } public Uri getContentUri() { return contentUri; } } // 修正后的加载方法 private void loadDocumentsWithMediaStore() { ArrayList<Document> documentList = new ArrayList<>(); Uri collection = MediaStore.Files.getContentUri("external"); String[] projection = { MediaStore.Files.FileColumns._ID, MediaStore.Files.FileColumns.DISPLAY_NAME, MediaStore.Files.FileColumns.MIME_TYPE }; // 补充PPT、Excel的MIME类型,覆盖需求中的所有文档格式 String selection = MediaStore.Files.FileColumns.MIME_TYPE + " IN (?, ?, ?, ?, ?, ?, ?, ?)"; String[] selectionArgs = new String[]{ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "text/plain", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }; try (Cursor cursor = getContentResolver().query(collection, projection, selection, selectionArgs, null)) { if (cursor != null && cursor.moveToFirst()) { int idIndex = cursor.getColumnIndex(MediaStore.Files.FileColumns._ID); int nameIndex = cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME); do { long fileId = cursor.getLong(idIndex); String fileName = cursor.getString(nameIndex); // 通过Content Uri访问文件,避免路径权限问题 Uri contentUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), fileId); documentList.add(new Document(fileName, contentUri)); } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } // 初始化Adapter并绑定数据 DocumentAdapter documentAdapter = new DocumentAdapter(documentList, document -> { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(document.getContentUri(), getContentResolver().getType(document.getContentUri())); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent); }); recyclerView.setAdapter(documentAdapter); }
3. 适配Adapter支持自定义Document类
修改DocumentAdapter以适配新的Document对象:
public class DocumentAdapter extends RecyclerView.Adapter<DocumentAdapter.DocumentViewHolder> { private final ArrayList<Document> documentList; private final OnDocumentClickListener listener; public DocumentAdapter(ArrayList<Document> documentList, OnDocumentClickListener listener) { this.documentList = documentList; this.listener = listener; } @NonNull @Override public DocumentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false); return new DocumentViewHolder(view); } @Override public void onBindViewHolder(@NonNull DocumentViewHolder holder, int position) { Document document = documentList.get(position); holder.textView.setText(document.getName()); holder.itemView.setOnClickListener(v -> listener.onDocumentClick(document)); } @Override public int getItemCount() { return documentList.size(); } public interface OnDocumentClickListener { void onDocumentClick(Document document); } static class DocumentViewHolder extends RecyclerView.ViewHolder { TextView textView; public DocumentViewHolder(@NonNull View itemView) { super(itemView); textView = itemView.findViewById(android.R.id.text1); } } }
4. 修正权限回调逻辑
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted) { loadDocumentsWithMediaStore(); } else { Toast.makeText(this, "权限被拒绝,无法读取文档", Toast.LENGTH_SHORT).show(); } } }
额外配置
在AndroidManifest.xml中添加权限声明:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" android:minSdkVersion="33"/> <uses-permission android:name="android.permission.READ_MEDIA_DOCUMENTS" android:minSdkVersion="33"/>
内容的提问来源于stack exchange,提问作者Progromatic World
相关产品推荐
相关产品推荐

