媒体扫描仪,用于Android Q上的辅助存储

fil*_*tto 10 android mediastore android-contentresolver android-external-storage android-10.0

随着更新的Android Q的出现,许多事情发生了变化,尤其是在范围存储和file:///URI 逐渐弃用的情况下。问题是缺少有关如何在Android Q设备上正确处理媒体文件的文档。

我有一个媒体文件(音频)管理应用程序,但找不到一种可靠的方法来告诉OS我对文件进行了更改,以便它可以更新其MediaStore记录。

选项1:MediaScannerService

MediaScannerConnection.scanFile(context, new String[]{ filePath }, new String[]{"audio/*"}, new MediaScannerConnection.OnScanCompletedListener() {
    @Override
    public void onScanCompleted(String s, Uri uri) {

    }
});
Run Code Online (Sandbox Code Playgroud)
  • 使用file://主存储中的URI
  • 不适用于file://辅助存储(例如可移动存储)中的URI
  • 不适用于任何content://URI

选项2:广播

context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
Run Code Online (Sandbox Code Playgroud)
  • 根本不工作
  • 很快弃用

选项3:手动插入MediaStore

AudioFileContentValues是来自的某些列值MediaStore.Audio.AudioColumns

基于file://URI的旧方法:

Uri uri = MediaStore.Audio.Media.getContentUriForPath(file_path);
newUri = context.getContentResolver().insert(uri, AudioFileContentValues);
Run Code Online (Sandbox Code Playgroud)
  • MediaStore.Audio.Media.getContentUriForPath 不推荐使用
  • 还是行不通

基于我可以从文档中得出的新方法:

Uri collection = MediaStore.Audio.Media.getContentUri(correctVolume);
newUri = context.getContentResolver().insert(collection, AudioFileContentValues);
Run Code Online (Sandbox Code Playgroud)

哪里correctVolumeexternal从主存储,同时它会是这样的0000-0000二级存储,根据该文件的位置。

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

这些或多或少是早期Android版本中可用的所有方法,但是现在它们都不允许我通知系统我更改了一些音频文件元数据,并让Android更新MediaStore记录。尽管选项1可以部分起作用,但由于它显然不支持内容URI,因此这永远不是一个有价值的解决方案。

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

Max*_*Max 5

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

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

现在,对媒体的每次更改都必须发生在 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
}
Run Code Online (Sandbox Code Playgroud)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

当您不指定 MIME 类型时,一件事似乎会假定 mp3 是 MIME 类型。因此 .flac 文件将保存为 name.flac.mp3,因为如果没有 mp3 文件类型,它会添加 mp3 文件类型,并且它认为它是 mp3。它不会为 mp3 文件添加另一个 .mp3。我目前在任何地方都没有 MIME 类型...所以我想我现在要继续执行此操作。

还有一个关于作用域/共享存储的有用的 google IO 讨论https://youtu.be/3EtBw5s9iRY

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


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