
android 11 及以上版本因分区存储(scoped storage)限制,使用 `action_pick` + `extra_output` 方式会导致“access denied”错误;应改用 `action_get_content` 获取只读 uri,并通过 `contentresolver` 安全读取图像数据。
在 Android 11(API 30)及更高版本中,Google 强制推行 分区存储(Scoped Storage),大幅收紧对共享存储(如 DCIM、Pictures)的直接文件路径访问权限。你当前代码中使用 Intent.ACTION_PICK 并传入 EXTRA_OUTPUT 的方式,在 Android 11+ 上已被系统拒绝写入权限——即使你声明了 READ_MEDIA_IMAGES,MediaStore 也不再允许第三方 App 向外部 URI 写入临时结果,因此会触发 “Access denied” 错误。
✅ 正确做法:放弃 ACTION_PICK + EXTRA_OUTPUT,改用 ACTION_GET_CONTENT
该方式不尝试写入外部 URI,而是由系统返回一个具有临时读取权限的 content:// URI(如 content://media/...),App 可通过 ContentResolver 安全读取图像数据,完全符合 Android 11+ 的隐私与存储规范。
✅ 推荐实现(现代 AndroidX Activity Result API)
// Kotlin 示例(推荐)
private val pickerActivityResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK && result.data?.data != null) {
val photoUri: Uri = result.data!!.data!!
// ✅ 安全获取 Bitmap(无需文件路径!)
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, photoUri)
imageView.setImageBitmap(bitmap)
// ✅ 或转换为 File(如需保存/上传)
val inputStream = contentResolver.openInputStream(photoUri)
val tempFile = File(cacheDir, "picked_${System.currentTimeMillis()}.jpg")
inputStream?.use { input ->
FileOutputStream(tempFile).use { output ->
input.copyTo(output)
}
}
Log.d("Picker", "Saved to: ${tempFile.absolutePath}")
}
}// Java 示例(兼容写法) private final ActivityResultLauncherpickerActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK && result.getData() != null) { Uri photoUri = result.getData().getData(); try { Bitmap bitmap = MediaStore.Images.Media.getBitmap( getContentResolver(), photoUri); imageView.setImageBitmap(bitmap); // 如需保存为本地文件(推荐缓存目录) InputStream is = getContentResolver().openInputStream(photoUri); File tempFile = new File(getCacheDir(), "picked_" + System.currentTimeMillis() + ".jpg"); try (FileOutputStream os = new FileOutputStream(tempFile)) { is.transferTo(os); } Log.d("Picker", "Saved: " + tempFile.getAbsolutePath()); } catch (Exception e) { e.printStackTrace(); } } });
调用选取器:
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "image/*"
// 可选:限定格式(如仅 JPEG)
// type = "image/jpeg"
}
pickerActivityResultLauncher.launch(Intent.createChooser(intent, "选择照片"))⚠️ 关键注意事项
- ❌ 不要使用 ACTION_PICK + EXTRA_OUTPUT:Android 11+ 已废弃此组合,强制返回 null 或抛出安全异常;
- ✅ 无需 WRITE_EXTERNAL_STORAGE 或 READ_EXTERNAL_STORAGE(API 33+ 已弃用),只需声明:
- ✅ ACTION_GET_CONTENT 返回的 content:// URI 自动附带 临时读取权限(grantUriPermission),无需手动 takePersistableUriPermission(除非需长期访问);
- ? 若需裁剪,避免使用过时的 com.android.camera.action.CROP(厂商兼容性差、无权限保障)。推荐使用 AndroidX Crop Library 或 Bitmap 内存裁剪;
- ? 不要尝试用 Cursor 查询 MediaStore.Images.Media.DATA 字段(Android 10+ 已返回 null)——这是你原代码中 picturePath 为空的根本原因。
✅ 总结
| 方案 | Android 11+ 兼容性 | 安全性 | 推荐度 |
|---|---|---|---|
| ACTION_PICK + EXTRA_OUTPUT | ❌ 失败(Access Denied) | 低 | ⛔ 不可用 |
| ACTION_GET_CONTENT(现代 API) | ✅ 完全支持 | 高(URI 权限沙箱) | ✅ 强烈推荐 |
| ActivityResultLauncher + getBitmap() | ✅ 简洁可靠 | ✅ | ✅ 最佳实践 |
遵循上述方案,即可彻底解决 Android 11+ 图库选图“Access Denied”问题,同时满足 Google Play 政策与用户隐私要求。









