Android API 30下调用.delete()无法删除文件如何解决
Android API 30+ 文件删除适配方案
API 30(Android 11)开始强制执行分区存储规则,应用默认仅可直接操作自身沙盒目录下的文件、以及媒体库中自身创建的媒体文件,删除非自身生成的公共目录文件需要适配对应权限流程。
可按你的业务场景选择以下适配方案:
1. 原始文件为应用自身生成的场景
- 优先将原始文件存在应用专属沙盒路径,无需额外权限即可直接调用
delete()删除:- 内部沙盒路径:
/data/data/[应用包名]/files/ - 外部沙盒路径:
/sdcard/Android/[应用包名]/files/
- 内部沙盒路径:
- 若文件存在公共媒体目录,只要是当前应用写入的文件,API 30+仍可直接删除,删除失败可检查文件写入时的归属是否正常。
2. 原始文件来自其他应用/公共目录的场景
方案A:申请全文件管理权限(仅适用于文件管理类应用)
该权限Google Play审核规则严格,非文件管理类应用无法通过审核,适配步骤:
- 清单文件声明权限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
- 跳转系统页引导用户手动授权:
if (Environment.isExternalStorageManager()) { // 已获取权限,可直接操作任意公共目录文件 } else { val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:${packageName}") startActivityForResult(intent, 1001) }
方案B:走系统媒体删除授权流程(适用于普通图片类应用)
图片属于公共媒体库内容,可通过MediaStore唤起系统确认弹窗,用户同意后即可完成删除,适配代码:
// 将源文件路径转为MediaStore对应的Uri val resolver = contentResolver val imageContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI val selection = MediaStore.Images.Media.DATA + "=?" val selectionArgs = arrayOf(sourceFile.absolutePath) try { // 先尝试直接删除,自身创建的文件可直接成功 resolver.delete(imageContentUri, selection, selectionArgs) } catch (securityException: SecurityException) { // 捕获权限异常,走系统授权删除流程 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val deleteUris = mutableListOf<Uri>() // 查询对应文件的MediaStore Uri val cursor = resolver.query(imageContentUri, arrayOf(MediaStore.Images.Media._ID), selection, selectionArgs, null) cursor?.use { if (it.moveToFirst()) { val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)) val uri = ContentUris.withAppendedId(imageContentUri, id) deleteUris.add(uri) } } // 唤起系统删除确认弹窗 val deleteIntent = MediaStore.createDeleteRequest(resolver, deleteUris) startIntentSenderForResult(deleteIntent.intentSender, 1002, null, 0, 0, 0) } } // 收到用户授权回调后再执行后续业务逻辑
原有moveFile代码优化建议
建议将目标存储路径改为应用专属沙盒路径,无需额外权限即可读写,符合分区存储规范,调整后代码:
private fun moveFile(file: File, status: Boolean) { // 用系统API获取应用专属外部存储路径,避免自行拼接绝对路径适配问题 val destRoot = getExternalFilesDir(null)?.absolutePath ?: filesDir.absolutePath val path = if (status) { "$destRoot${File.separator}Historial imágenes subidas${File.separator}Success${File.separator}" } else { "$destRoot${File.separator}Historial imágenes subidas${File.separator}Error${File.separator}" } try { file.let { sourceFile -> val destinationPath = File(path + file.name) if (!destinationPath.exists()) { sourceFile.copyTo(destinationPath) // 此处替换为上文适配API30的删除逻辑即可 deleteSourceFile(sourceFile) } else { deleteSourceFile(sourceFile) } } } catch (e: Exception) { Toast.makeText(this, e.message.toString(), Toast.LENGTH_SHORT).show() } }
内容的提问来源于stack exchange,提问作者Nicolas Machuca
相关产品推荐
相关产品推荐

