RecyclerView调用setImageUri无法显示图片:权限异常求助
嘿,这个问题我之前踩过坑!你碰到的SecurityException本质是系统媒体库的Uri权限是临时的——当你用ACTION_OPEN_DOCUMENT这类API选图片时,系统只给了临时访问权限,一旦App重启或者过段时间,这个权限就会被收回,直接存Uri字符串再调用setImageURI()自然就访问不了了。
给你两个靠谱的解决方案,按需选择:
方案一:获取持久化的Uri访问权限
这个方案保留Uri的存储方式,核心是在用户选择图片时,主动申请持久化权限,让App后续能一直访问这个Uri:
1. 在获取图片Uri时申请持久化权限
如果你用的是传统的onActivityResult:
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && data != null) { Uri imageUri = data.getData(); if (imageUri != null) { // 提取系统返回的权限标记 int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // 申请持久化权限 getContentResolver().takePersistableUriPermission(imageUri, takeFlags); // 现在把这个Uri字符串存到Room里就没问题了 } } }
如果用Jetpack的ActivityResultContracts(更推荐,适配新版Android):
// 注册图片选择器 ActivityResultLauncher<String[]> pickImageLauncher = registerForActivityResult( new ActivityResultContracts.OpenDocument(), selectedUri -> { if (selectedUri != null) { // 申请持久化读写权限 int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION; getContentResolver().takePersistableUriPermission(selectedUri, takeFlags); // 存储Uri到Room数据库 } } ); // 启动选择器时调用 pickImageLauncher.launch(new String[]{"image/*"});
2. 确保Manifest里的权限声明
虽然ACTION_OPEN_DOCUMENT不需要WRITE_EXTERNAL_STORAGE(Android 10+),但还是要加上基础的读取权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 如果需要修改图片,加上这个(仅针对Android 12及以下) --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
处理完这些,你原来RecyclerView里的代码就能正常工作了,因为Uri的访问权限已经被持久化保存了。
方案二:复制图片到App私有目录(彻底避免权限问题)
如果你觉得处理Uri权限太繁琐,可以把选中的图片复制到App的私有存储目录,然后存储这个私有路径,这样完全不需要担心系统权限限制:
1. 复制图片到私有目录的方法
private String copyImageToPrivateStorage(Uri sourceUri) { try { InputStream inputStream = getContentResolver().openInputStream(sourceUri); // App私有目录,只有当前App能访问,卸载时会自动删除 File privateDir = getFilesDir(); // 生成唯一的文件名,避免重复 File targetFile = new File(privateDir, "student_img_" + System.currentTimeMillis() + ".png"); // 复制文件内容 OutputStream outputStream = new FileOutputStream(targetFile); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } // 关闭流 inputStream.close(); outputStream.close(); // 返回私有路径,存到Room里 return targetFile.getAbsolutePath(); } catch (IOException e) { e.printStackTrace(); return null; } }
2. 在RecyclerView里加载图片
把上面返回的路径存到Room后,显示时直接用这个路径:
@Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { Student current = students.get(position); holder.nameItemView.setText(current.getName()); holder.numberItemView.setText(current.getNumber()); String imagePath = current.getImagePath(); File imageFile = new File(imagePath); if (imageFile.exists()) { // 直接加载私有目录的图片 holder.studImage.setImageURI(Uri.fromFile(imageFile)); // 更推荐用Glide/Picasso加载,处理图片缩放和缓存更高效 // Glide.with(holder.studImage.getContext()).load(imageFile).into(holder.studImage); } }
这个方案的好处是完全绕开了系统媒体库的权限问题,而且不会留下垃圾文件。
为什么原来的代码会报错?
你存储的content:/com.android.providers.media.documents/document/image%3A42这种Uri,是系统媒体库提供的临时访问入口,它的权限只在你选择图片的那次会话中有效。当你重启App或者过段时间再读取这个Uri时,系统已经收回了访问权限,所以就会抛出Permission Denial的异常。
内容的提问来源于stack exchange,提问作者Hessam-Emami

