Scoped Storage:如何通过 MediaStore 删除多个音频文件?
Scoped Storage: how to delete multiple audio files via MediaStore?
我正在尝试从设备的外部存储(例如 /storage/emulated/0/Music
文件夹中)删除音频文件。
在分析了 MediaStore
sample 之后,我针对 API 28 及更早版本得出了以下解决方案:
fun deleteTracks(trackIds: LongArray): Int {
val whereClause = buildWildcardInClause(trackIds.size) // _id IN (?, ?, ?, ...)
return resolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, Array(trackIds.size) { trackIds[it].toString() })
}
我注意到上面的代码只从 MediaStore 数据库中删除曲目,而不是从设备中删除曲目(文件在重新启动后重新添加到 MediaStore 中)。所以我修改了该代码以查询 Media.DATA
列,然后使用该信息删除相关文件。这按预期工作。
但现在 Android Q 引入了分区存储,Media.DATA
(和 Albums.ALBUM_ART
)现已弃用,因为该应用程序可能无法访问这些文件。 ContentResolver.openFileDescriptor
只能读取文件,不能删除文件
那么从 Android Q 开始,推荐的删除曲目的方法是什么?
该示例未显示如何从 MediaStore
中删除多个文件,并且 MediaStore.Audio.Media
似乎与 MediaStore.Images.Media
.
的工作方式不同
在Android10中有两种方法可以做到这一点,这取决于应用程序AndroidManifest.xml
文件中android:requestLegacyExternalStorage
的值。
选项 1: 分区存储已启用(android:requestLegacyExternalStorage="false"
)
如果您的应用添加了开头的内容(使用contentResolver.insert
),那么上述方法(使用contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray)
)应该有效。在 Android 10 日,应用程序提交到媒体商店的任何内容都可以由该应用程序自由操作。
对于用户添加的内容,或者属于另一个应用程序的内容,过程要复杂一些。
要从媒体商店中删除单个项目,请不要将通用媒体商店 URI 传递给 delete
(即:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
),但是相反,使用项目的特定 URI。
context.contentResolver.delete(
audioContentUri,
"${MediaStore.Audio.Media._ID} = ?",
arrayOf(audioContentId)
(我很确定 'where' 子句没有必要,但它对我来说很有意义:)
在 Android 10 上,如果你的 targetSdkVersion
>=29,这可能会抛出一个 RecoverableSecurityException
。在这个异常中是一个 IntentSender
,它可以由你的 Activity
启动,以请求用户允许修改或删除它。 (发件人位于recoverableSecurityException.userAction.actionIntent.intentSender
)
由于用户必须对每个项目授予权限,因此无法批量删除它们。 (或者,可能会请求修改或删除整个专辑的权限,一次一首歌曲,然后使用上面使用的 delete
查询。此时您的应用程序将有权删除它们,并且Media Store 会一次完成所有这些操作。但是您必须先请求每首歌曲。)
选项 2: 启用传统存储(android:requestLegacyExternalStorage="true"
)
在这种情况下,只需调用 contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray)
不仅会从数据库中删除项目,还会删除文件。
该应用需要持有 WRITE_EXTERNAL_STORAGE
权限才能正常工作,但我测试了问题中的代码并确认文件也已删除。
因此,对于较旧的 API 版本,我认为您需要使用 contentResolver.delete
并自行取消链接文件(或取消链接文件并通过 [=28 提交重新扫描请求) =]).
对于 Android 10+,contentResolver.delete
将删除索引和内容本身。
在 Android 11 deleting/updating 中,更好更清洁的 API 支持多个媒体文件。
在Android10之前,我们必须通过创建文件对象来删除文件的物理副本,并使用[=31=删除MediaStore中的索引文件]()(或)对已删除的文件进行媒体扫描,这将删除它在 MediaStore 中的条目。
这就是它在 Android 10 os 以下的工作方式。如果您通过在清单 android:requestLegacyExternalStorage="true"
中指定它来选择退出范围存储,并且在 Android 10 中仍然可以正常工作
现在 Android 11 您被迫使用分区存储。如果您想删除任何非您创建的媒体文件,您必须获得用户的许可。您可以使用 MediaStore.createDeleteRequest() 获得权限。这将通过描述用户将要执行的操作来显示一个对话框,一旦授予权限,android 就会有一个内部代码来负责删除物理文件和 MediaStore 中的条目。
private void requestDeletePermission(List<Uri> uriList){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
PendingIntent pi = MediaStore.createDeleteRequest(mActivity.getContentResolver(), uriList);
try {
startIntentSenderForResult(pi.getIntentSender(), REQUEST_PERM_DELETE, null, 0, 0,
0);
} catch (SendIntentException e) { }
}
}
上面的代码会同时执行这两种操作,请求删除权限,并在授予权限后删除文件。
你会在 onActivityResult() 中得到结果
我正在尝试从设备的外部存储(例如 /storage/emulated/0/Music
文件夹中)删除音频文件。
在分析了 MediaStore
sample 之后,我针对 API 28 及更早版本得出了以下解决方案:
fun deleteTracks(trackIds: LongArray): Int {
val whereClause = buildWildcardInClause(trackIds.size) // _id IN (?, ?, ?, ...)
return resolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, Array(trackIds.size) { trackIds[it].toString() })
}
我注意到上面的代码只从 MediaStore 数据库中删除曲目,而不是从设备中删除曲目(文件在重新启动后重新添加到 MediaStore 中)。所以我修改了该代码以查询 Media.DATA
列,然后使用该信息删除相关文件。这按预期工作。
但现在 Android Q 引入了分区存储,Media.DATA
(和 Albums.ALBUM_ART
)现已弃用,因为该应用程序可能无法访问这些文件。 ContentResolver.openFileDescriptor
只能读取文件,不能删除文件
那么从 Android Q 开始,推荐的删除曲目的方法是什么?
该示例未显示如何从 MediaStore
中删除多个文件,并且 MediaStore.Audio.Media
似乎与 MediaStore.Images.Media
.
在Android10中有两种方法可以做到这一点,这取决于应用程序AndroidManifest.xml
文件中android:requestLegacyExternalStorage
的值。
选项 1: 分区存储已启用(android:requestLegacyExternalStorage="false"
)
如果您的应用添加了开头的内容(使用contentResolver.insert
),那么上述方法(使用contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray)
)应该有效。在 Android 10 日,应用程序提交到媒体商店的任何内容都可以由该应用程序自由操作。
对于用户添加的内容,或者属于另一个应用程序的内容,过程要复杂一些。
要从媒体商店中删除单个项目,请不要将通用媒体商店 URI 传递给 delete
(即:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
),但是相反,使用项目的特定 URI。
context.contentResolver.delete(
audioContentUri,
"${MediaStore.Audio.Media._ID} = ?",
arrayOf(audioContentId)
(我很确定 'where' 子句没有必要,但它对我来说很有意义:)
在 Android 10 上,如果你的 targetSdkVersion
>=29,这可能会抛出一个 RecoverableSecurityException
。在这个异常中是一个 IntentSender
,它可以由你的 Activity
启动,以请求用户允许修改或删除它。 (发件人位于recoverableSecurityException.userAction.actionIntent.intentSender
)
由于用户必须对每个项目授予权限,因此无法批量删除它们。 (或者,可能会请求修改或删除整个专辑的权限,一次一首歌曲,然后使用上面使用的 delete
查询。此时您的应用程序将有权删除它们,并且Media Store 会一次完成所有这些操作。但是您必须先请求每首歌曲。)
选项 2: 启用传统存储(android:requestLegacyExternalStorage="true"
)
在这种情况下,只需调用 contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray)
不仅会从数据库中删除项目,还会删除文件。
该应用需要持有 WRITE_EXTERNAL_STORAGE
权限才能正常工作,但我测试了问题中的代码并确认文件也已删除。
因此,对于较旧的 API 版本,我认为您需要使用 contentResolver.delete
并自行取消链接文件(或取消链接文件并通过 [=28 提交重新扫描请求) =]).
对于 Android 10+,contentResolver.delete
将删除索引和内容本身。
在 Android 11 deleting/updating 中,更好更清洁的 API 支持多个媒体文件。
在Android10之前,我们必须通过创建文件对象来删除文件的物理副本,并使用[=31=删除MediaStore中的索引文件]()(或)对已删除的文件进行媒体扫描,这将删除它在 MediaStore 中的条目。
这就是它在 Android 10 os 以下的工作方式。如果您通过在清单 android:requestLegacyExternalStorage="true"
中指定它来选择退出范围存储,并且在 Android 10 中仍然可以正常工作现在 Android 11 您被迫使用分区存储。如果您想删除任何非您创建的媒体文件,您必须获得用户的许可。您可以使用 MediaStore.createDeleteRequest() 获得权限。这将通过描述用户将要执行的操作来显示一个对话框,一旦授予权限,android 就会有一个内部代码来负责删除物理文件和 MediaStore 中的条目。
private void requestDeletePermission(List<Uri> uriList){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
PendingIntent pi = MediaStore.createDeleteRequest(mActivity.getContentResolver(), uriList);
try {
startIntentSenderForResult(pi.getIntentSender(), REQUEST_PERM_DELETE, null, 0, 0,
0);
} catch (SendIntentException e) { }
}
}
上面的代码会同时执行这两种操作,请求删除权限,并在授予权限后删除文件。
你会在 onActivityResult() 中得到结果