Android 11后无需MANAGE_EXTERNAL_STORAGE权限能否实现图片选裁存?
Android 11+ 无需 MANAGE_EXTERNAL_STORAGE 实现选图、裁剪与保存
可以实现该功能,无需禁用,以下是具体步骤:
1. 从图库选取图片
使用ACTION_GET_CONTENT(推荐)或ACTION_PICK启动图库,获取图片的content://类型Uri,无需直接访问文件路径:
代码实现(使用Activity Result API)
val pickImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { result.data?.data?.let { imageUri -> // 拿到图片Uri,下一步进行裁剪 startImageCrop(imageUri) } } } // 启动图库选择 fun pickImageFromGallery() { val intent = Intent(Intent.ACTION_GET_CONTENT).apply { type = "image/*" addCategory(Intent.CATEGORY_OPENABLE) } pickImageLauncher.launch(intent) }
2. 调用系统裁剪功能
Android 10+ 禁止直接传递file://Uri给其他应用,需通过FileProvider生成content://Uri作为裁剪输出目标:
第一步:配置FileProvider
- 在
AndroidManifest.xml中注册FileProvider:
<application> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
- 在
res/xml目录下创建file_paths.xml,指定共享目录:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <cache-path name="images" path="images/" /> </paths>
第二步:启动裁剪Intent
private val cropImageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { // 裁剪后的图片Uri就是我们之前传入的outputUri outputUri?.let { uri -> // 保存或使用裁剪后的图片 saveCroppedImage(uri) } } } private var outputUri: Uri? = null fun startImageCrop(inputUri: Uri) { // 创建裁剪后保存的文件(应用私有缓存目录,无需权限) val cropFile = File(cacheDir, "images/cropped_${System.currentTimeMillis()}.jpg") cropFile.parentFile?.mkdirs() outputUri = FileProvider.getUriForFile(this, "${packageName}.fileprovider", cropFile) val cropIntent = Intent("com.android.camera.action.CROP").apply { setDataAndType(inputUri, "image/*") putExtra("crop", "true") putExtra("aspectX", 1) // 裁剪比例,可按需调整 putExtra("aspectY", 1) putExtra("outputX", 500) // 输出尺寸 putExtra("outputY", 500) putExtra("scale", true) putExtra(MediaStore.EXTRA_OUTPUT, outputUri) putExtra("return-data", false) // 禁用返回小图,避免内存问题 addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) // 授予裁剪应用写入权限 } // 检查是否有应用能处理裁剪Intent val activities = packageManager.queryIntentActivities(cropIntent, 0) if (activities.isNotEmpty()) { cropImageLauncher.launch(cropIntent) } }
3. 保存裁剪后的图片
如果需要将图片保存到公共图库(无需MANAGE_EXTERNAL_STORAGE),可通过MediaStore插入:
fun saveCroppedImage(croppedUri: Uri) { contentResolver.openInputStream(croppedUri)?.use { inputStream -> val values = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, "cropped_${System.currentTimeMillis()}.jpg") put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyApp") } val insertUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) insertUri?.let { uri -> contentResolver.openOutputStream(uri)?.use { outputStream -> inputStream.copyTo(outputStream) // 保存成功,通知系统扫描更新图库 sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)) } } } }
关键注意事项
- 全程使用
content://Uri而非file://Uri,避免权限拒绝 - 裁剪输出优先使用应用私有目录(如
cacheDir、filesDir),无需任何存储权限 - 保存到公共目录时使用
MediaStore,不要直接操作外部存储路径 - 针对不同厂商的裁剪应用兼容性,可适当调整裁剪Intent的参数(部分厂商可能不支持某些参数)
内容的提问来源于stack exchange,提问作者tedo2113
相关产品推荐
相关产品推荐

