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

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支持申请持久化访问权限,允许后续多次访问目标文件。

解决步骤

  1. 替换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);
  1. 申请持久化权限
    在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"));
  1. RecyclerView加载逻辑无需修改
    你当前RecyclerAdapter中holder.image.setImageURI(Uri.parse(employee.getImg()))的代码可以保持不变,因为已经获取了持久化权限,现在能正常读取URI对应的图片。

额外注意事项

  • 持久化权限仅针对单个URI,每个选中的文件都需要单独申请。
  • 若用户卸载重装APP,之前获取的所有持久化权限会失效,需重新选择文件。

内容的提问来源于stack exchange,提问作者Abdullah Alrayyis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 20:27:48