Android权限拒绝异常求助:文件选择器选图后RecyclerView显示报错
java.lang.SecurityException: Permission Denial: reading
com.android.providers.media.MediaDocumentsProvider uri
content://com.android.providers.media.documents/document/image%3A41
from pid=10501, uid=10171 requires that you obtain access using
ACTION_OPEN_DOCUMENT or related APIs
这是我从文件选择器而非图库选择图片时触发的错误。我用以下代码打开选择器:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); startActivityForResult(Intent.createChooser(intent, "choose picture"), PICK_IMAGE_REQUEST_CODE);
选中图片后把URI存入数据库,之后在RecyclerView中展示时,调用onBindViewHolder方法触发了上述权限错误。
RecyclerAdapter代码如下:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.Holder> { private ArrayList<Employee> employees; public RecyclerAdapter(ArrayList<Employee> employees) { this.employees = employees; } @NonNull @Override public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.employee_custom_item, null, false); Holder holder = new Holder(view); return holder; } @Override public void onBindViewHolder(@NonNull Holder holder, int position) { Employee employee = employees.get(position); holder.image.setImageURI(Uri.parse(employee.getImg())); holder.name.setText(employee.getName()); holder.age.setText(String.valueOf(employee.getAge())); holder.hour.setText(String.valueOf(employee.getHour())); } @Override public int getItemCount() { return employees.size(); } class Holder extends RecyclerView.ViewHolder{ ImageView image; TextView name, age, hour; public Holder(@NonNull View itemView) { super(itemView); image = itemView.findViewById(R.id.employee_picture); name = itemView.findViewById(R.id.employee_name); age = itemView.findViewById(R.id.employee_age); hour = itemView.findViewById(R.id.employee_hour); } } }
问题原因
ACTION_GET_CONTENT获取的URI仅拥有临时访问权限,仅限当前会话有效。当你把URI存入数据库,后续重启APP或再次加载时,权限已失效,读取时就会触发权限拒绝错误。而ACTION_OPEN_DOCUMENT支持申请持久化访问权限,允许后续多次访问目标文件。
解决步骤
- 替换Intent的Action
将Intent.ACTION_GET_CONTENT替换为Intent.ACTION_OPEN_DOCUMENT,同时添加Intent.CATEGORY_OPENABLE确保选择的是可访问的文件:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); startActivityForResult(Intent.createChooser(intent, "choose picture"), PICK_IMAGE_REQUEST_CODE);
- 申请持久化权限
在onActivityResult(AndroidX建议用registerForActivityResult)中,对返回的URI申请持久化读写权限:
传统onActivityResult写法:
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PICK_IMAGE_REQUEST_CODE && resultCode == RESULT_OK && data != null) { Uri uri = data.getData(); if (uri != null) { // 提取权限标记并申请持久化权限 int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); getContentResolver().takePersistableUriPermission(uri, takeFlags); // 此时再将URI存入数据库 } } }
AndroidX Activity Result API写法:
ActivityResultLauncher<Intent> pickImageLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK && result.getData() != null) { Uri uri = result.getData().getData(); if (uri != null) { int takeFlags = result.getData().getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); getContentResolver().takePersistableUriPermission(uri, takeFlags); // 存入数据库 } } }); // 调用选择器时 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); pickImageLauncher.launch(Intent.createChooser(intent, "choose picture"));
- RecyclerView加载逻辑无需修改
你当前RecyclerAdapter中holder.image.setImageURI(Uri.parse(employee.getImg()))的代码可以保持不变,因为已经获取了持久化权限,现在能正常读取URI对应的图片。
额外注意事项
- 持久化权限仅针对单个URI,每个选中的文件都需要单独申请。
- 若用户卸载重装APP,之前获取的所有持久化权限会失效,需重新选择文件。
内容的提问来源于stack exchange,提问作者Abdullah Alrayyis
相关产品推荐
相关产品推荐

