用于 Android Q 上的辅助存储的媒体扫描仪
Posted
技术标签:
【中文标题】用于 Android Q 上的辅助存储的媒体扫描仪【英文标题】:Media scanner for secondary storage on Android Q 【发布时间】:2019-09-27 10:51:05 【问题描述】:随着较新的 android Q,许多事情发生了变化,尤其是在范围存储和逐渐弃用 file:///
URI 的情况下。问题是缺乏关于如何在 Android Q 设备上正确处理媒体文件的文档。
我有一个媒体文件(音频)管理应用程序,但我找不到可靠的方法告诉操作系统我对文件执行了更改,以便它可以更新其 MediaStore 记录。
选项 #1:MediaScannerService
MediaScannerConnection.scanFile(context, new String[] filePath , new String[]"audio/*", new MediaScannerConnection.OnScanCompletedListener()
@Override
public void onScanCompleted(String s, Uri uri)
);
与来自主存储的 file://
URI 一起使用
不适用于来自辅助存储(例如可移动存储)的 file://
URI
不适用于任何 content://
URI
选项 #2:广播
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
根本不工作
即将弃用
选项 #3:手动插入 MediaStore
AudioFileContentValues
是来自MediaStore.Audio.AudioColumns
的一些列值。
基于file://
URI 的旧方法:
Uri uri = MediaStore.Audio.Media.getContentUriForPath(file_path);
newUri = context.getContentResolver().insert(uri, AudioFileContentValues);
MediaStore.Audio.Media.getContentUriForPath
已弃用
还是不行
基于我可以从documentation 汇总的新方法:
Uri collection = MediaStore.Audio.Media.getContentUri(correctVolume);
newUri = context.getContentResolver().insert(collection, AudioFileContentValues);
其中correctVolume
将是来自主存储的external
,而类似于0000-0000
用于辅助存储,具体取决于文件所在的位置。
content://media/external/audio/media/125
,但不会在 MediaStore 中为位于主存储中的文件保留任何记录
插入失败,没有返回 URI 并且 MediaStore 中没有记录
这些或多或少是以前 Android 版本中可用的所有方法,但现在它们都不允许我通知系统我更改了一些音频文件元数据并让 Android 更新 MediaStore 记录。事件虽然选项 #1 部分有效,但这绝不可能是一个有价值的解决方案,因为它显然不支持内容 URI。
是否有任何可靠的方法可以在 Android Q 上触发媒体扫描,尽管文件位于何处?根据 Google 的说法,我们甚至不应该关心文件位置,因为我们很快将只使用内容 URI。 MediaStore 在我看来一直有点令人沮丧,但现在情况更糟了。
【问题讨论】:
“我还没有找到一种可靠的方法告诉操作系统我对文件进行了更改,以便它可以更新其 MediaStore 记录”——如果MediaStore
已经知道内容,我认为您需要在选项#3 中使用update()
而不是insert()
。 insert()
如果您正在创建新内容。
@CommonsWare 在测试中,我正在处理在 MediaStore 中还没有自己的行的音频文件,因为它们一次都没有被扫描过。无论如何,我感谢您的博客以及您如何让社区了解即将发生的 Android 行为变化。
【参考方案1】:
我目前也在为此苦苦挣扎。
我认为,一旦您在 Android Q 上,您想要做的事情就无法再做,因为您无法访问 Q 上的音乐目录。您只能在您创建的目录中创建和访问文件。您没有创建音乐目录。
现在对 Media 的每一次更改都必须发生在 MediaStore 中。所以你事先插入你的音乐文件,然后从 MediaStore 获取一个输出流来写入它。对 Q 的所有更改都应该在 MediaStore 上完成,因此您通知 MediaStore 更改甚至不能再发生,因为您永远不会直接访问文件。
这有一个巨大的问题,因为在旧版本的 Android 中不存在 MediaStore 中使这成为可能的所有新事物。因此,我目前确实认为您将需要两次实施所有内容,遗憾的是。至少如果您想积极影响音乐的保存位置。
这两个 MediaStore 列是 Q 中的新列,在 Q 之前不存在,你可能需要在 Q 中使用
MediaStore.Audio.Media.RELATIVE_PATH
可以影响保存路径。所以我把“Music/MyAppName/MyLibraryName”放在那里,最终将“song.mp3”保存到“Music/MyAppName/MyLibraryName/song.mp3”中
MediaStore.Audio.Media.IS_PENDING
这应该在歌曲仍在编写时设置为 1,然后您可以将其更新为 0。
我现在也开始使用 if 检查 Android 版本来实现两次。这很烦人。我不想这样做。但似乎这是唯一的方法。
我将在此处添加一些代码,说明我如何在 Android.Q 及以下版本中插入音乐。这并不完美。我必须为 Q 指定 MIME 类型,因为 flac 现在会以某种方式变为 .flac.mp3,因为它似乎不太明白。
所以,无论如何,这是我已经更新的部分,可以与 Q 一起使用,之前它会从我 NAS 上的音乐播放器下载音乐文件。该应用程序是用 kotlin 编写的,不确定这对您来说是否有问题。
override fun execute(library : Library, remoteApi: RemoteApi, ctx: Context) : Boolean
var success = false
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
val values = ContentValues().apply
put(MediaStore.Audio.Media.RELATIVE_PATH, library.rootFolderRelativePath)
put(MediaStore.Audio.Media.DISPLAY_NAME, remoteLibraryEntry.getFilename())
put(MediaStore.Audio.Media.IS_PENDING, 1)
val collection = MediaStore.Audio.Media
.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val uri = ctx.contentResolver.insert(collection, values)
ctx.contentResolver.openOutputStream(uri!!).use
success = remoteApi.downloadMusic(remoteLibraryEntry, it!!)
if(success)
values.clear()
val songId = JDrop.mediaHelper.getSongId(uri)
JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
values.put(MediaStore.Audio.Media.IS_PENDING, 0)
ctx.contentResolver.update(uri, values, null, null)
else
ctx.contentResolver.delete(uri, null, null)
else
val file = File("$library.rootFolderPublicDirectory/$remoteLibraryEntry.getFilename()")
if(file.exists()) file.delete()
success = remoteApi.downloadMusic(remoteLibraryEntry, file.outputStream())
if (success)
MediaScannerConnection.scanFile(ctx, arrayOf(file.path), arrayOf("audio/*")) _, uri ->
val songId = JDrop.mediaHelper.getSongId(uri)
JDrop.db.music.insert(Music(mediaStoreId = songId, remoteId = remoteLibraryEntry.remoteId, libraryId = library.id))
return success
MediaStoreHelper 方法在这里
fun getSongId(uri : Uri) : Long
val cursor = resolver.query(uri, arrayOf(Media._ID), null, null, null)
return if(cursor != null && cursor.moveToNext())
val idIndex = cursor.getColumnIndex(Media._ID)
val id = cursor.getLong(idIndex)
cursor.close()
id
else
cursor?.close()
-1
当您没有指定 MIME 类型时,它似乎假定 mp3 是 MIME 类型。所以 .flac 文件将保存为 name.flac.mp3,因为如果没有,它会添加 mp3 文件类型并且它认为它是 mp3。它不会为 mp3 文件添加另一个 .mp3。我目前在任何地方都没有 MIME 类型……所以我想我现在就去做吧。
还有一个有用的 google IO 讨论关于作用域/共享存储https://youtu.be/3EtBw5s9iRY
这可能无法回答您的所有问题。果然不适合我。但是,粗略了解他们一开始所做的更改是一个有益的开始。
对于删除和更新文件,如果您在媒体存储条目上调用 delete,则在 Q 上它有点相同,该文件将被删除。在此之前,Q 您还必须手动删除该文件。但是如果你在 Q 上这样做,你的应用程序就会崩溃。因此,您必须再次检查您是否使用 Q 或旧版本的 android 并采取适当的措施。
【讨论】:
以上是关于用于 Android Q 上的辅助存储的媒体扫描仪的主要内容,如果未能解决你的问题,请参考以下文章