android - Android Q 上用于辅助存储的媒体扫描器

标签 android android-contentresolver mediastore android-external-storage android-10.0

在更新的 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 插入

AudioFileContentValuesMediaStore.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,具体取决于文件位于。

  • 插入返回一个内容 URI,例如 content://media/external/audio/media/125,但是对于位于主存储中的文件,MediaStore 中没有保留任何记录
  • 插入失败,没有返回 URI,MediaStore 中也没有记录

这些或多或少是以前 Android 版本中可用的所有方法,但现在没有一个允许我通知系统我更改了一些音频文件元数据并让 Android 更新 MediaStore 记录。尽管选项 #1 部分有效,但这永远不是一个有值(value)的解决方案,因为它显然不支持内容 URI。

无论文件位于何处,是否有可靠的方法在 Android Q 上触发媒体扫描?根据 Google 的说法,我们甚至不应该关心文件位置,因为我们很快将只使用内容 URI。在我看来,MediaStore 一直有点令人沮丧,但现在情况更糟。

最佳答案

我目前也在为此苦苦挣扎。

我认为一旦您使用了 Android Q,您就不能再做您想做的事情,因为不允许您在 Q 上访问音乐目录。您只能在您创建的目录中创建和访问文件。您没有创建音乐目录。

现在对媒体的每一次改变都必须发生在 MediaStore 上。所以你事先插入你的音乐文件,然后从 MediaStore 中获取一个输出流来写入它。 Q on Media 上的所有更改都应该通过 MediaStore 完成,因此您通知 MediaStore 更改甚至不能再发生,因为您永远不会直接访问文件。

这有一个巨大的缺陷,即 MediaStore 中所有使这成为可能的新事物在旧版本的 Android 中都不存在。所以我目前确实认为您需要将所有内容实现两次,很遗憾。至少如果您想积极影响您的音乐保存位置。

这两个 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 类型,因为 flacs 现在会以某种方式变成 .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 文件添加另一个 .mp3。我目前在任何地方都没有 MIME 类型...所以我想我现在要继续做这个。

还有一个有用的 google IO 讨论范围/共享存储 https://youtu.be/3EtBw5s9iRY

这可能无法回答您的所有问题。它肯定不适合我。但这是一个有帮助的开端,可以粗略地了解他们甚至从一开始就做了哪些改变。


删除和更新文件在 Q 上有点相同,如果您在媒体存储条目上调用 delete,文件将被删除。之前,Q 你也必须手动删除文件。但如果你在 Q 上这样做,你的应用程序将会崩溃。因此,您必须再次检查您是否使用 Q 或旧版本的 android 并采取适当的措施。

关于android - Android Q 上用于辅助存储的媒体扫描器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56062168/

相关文章:

android - 从 SharedPreferences 中删除条目 - Android

android - 基于 Android Tab 的 Activity 中的 LogCat 错误 “you must specify a way to create tab indicator”

android - 应用程序在旋转时死于 “Sending signal. SIG: 9”

android - ContentResolver.SYNC_EXTRAS_EXPEDITED 的行为是否在任何地方定义?

android, MediaScanner vs FileObserver 用于监控创建的媒体文件

android - 如何组合图像中的多个 Uri

android - SurfaceView调用期的OnDraw

android - 如何取消contentresolver applyBatch?

android - 更新 Poweramp 播放列表

android - 下载图像并将其存储在android中的图库应用程序中