用于Android Q

时间:2019-05-09 14:52:52

标签: android android-contentresolver mediastore android-external-storage androidq

随着更新的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) {

    }
});
  • 使用主存储中的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可以部分起作用,但由于它显然不支持内容URI,所以这永远不是一个有价值的解决方案。

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

1 个答案:

答案 0 :(得分:0)

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

我认为一旦您在Android Q上,您想做的事无法再做一次,因为不允许您访问Q上的音乐目录。仅允许您在创建的目录中创建和访问文件。您尚未创建音乐目录。

现在,必须对Media进行所有更改,否则将MediaStore丢掉了。因此,您需要事先插入音乐文件,然后从MediaStore获取输出流以对其进行写入。对Media on 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。

我现在也开始对两次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。我想我现在什么地方都没有MIME类型...所以我现在就继续做。

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

那可能不会回答您所有的问题。确实足够不适合我。但这是一个有用的开始,让我们大致了解他们一开始所做的更改。


如果在媒体存储条目上调用delete,则删除和更新文件的种类与Q相同,该文件将被删除。之前,您还必须手动删除该文件。但是,如果您在Q上执行此操作,则您的应用将崩溃。因此,您必须再次检查是否在Q或更旧版本的android上,然后采取适当的措施。