23

随着较新的 ​​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已弃用
  • 还是行不通

基于我可以从文档中汇总的新方法:

Uri collection = MediaStore.Audio.Media.getContentUri(correctVolume);
newUri = context.getContentResolver().insert(collection, AudioFileContentValues);

来自主存储的位置,而类似于辅助存储的位置,取决于文件所在的位置correctVolumeexternal0000-0000

  • 插入返回一个内容 URI,例如,content://media/external/audio/media/125但没有记录保存在 MediaStore 中,用于位于主存储中的文件
  • 插入失败,没有返回 URI 并且 MediaStore 中没有记录

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

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

4

1 回答 1

5

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

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

现在,对 Media 的每一次更改都必须发生在 MediaStore 中。所以你事先插入你的音乐文件,然后从 MediaStore 获取一个输出流来写入它。媒体 Q 上的所有更改都应该扔到 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 类型,因为 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 类型......所以我想我现在要继续这样做。

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

这可能不会回答你所有的问题。果然不适合我。但这是一个有帮助的开始,让他们大致了解他们一开始所做的改变。


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

于 2019-07-20T19:42:56.990 回答