从 RELATIVE_PATH + DISPLAY_NAME 获取媒体的 URI 或 ID

Get URI or ID for Media from RELATIVE_PATH + DISPLAY_NAME

我正在尝试简单地添加音频文件。我的解决方案大多有效。但是它不起作用的地方是文件已经存在于 MediaStore 中。一旦我更仔细地查看 只有 存在于 MediaStore 中,该位置的设备上没有文件。

val values = ContentValues().apply {
   put(MediaStore.Audio.Media.RELATIVE_PATH, libraryPart.rootFolderRelativePath) // JDrop/1/1
   put(MediaStore.Audio.Media.DISPLAY_NAME, remoteLibraryEntry.getFilename()) //12.mp3
   put(MediaStore.Audio.Media.IS_PENDING, 1)
   if(mimeType != null)
       put(MediaStore.Audio.Media.MIME_TYPE, mimeType) // audio/mpeg3
}

val collection = MediaStore.Audio.Media
       .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

var uri = ctx.contentResolver.insert(collection, values) // returns null for around 300/2000 files consistently

Logcat 尝试插入该新文件时输出以下内容。

2020-01-24 22:27:33.724 4015-7707/? E/SQLiteDatabase: Error inserting title_key= bucket_display_name=1 owner_package_name=shio.at.jdrop parent=79657 volume_name=external_primary title_resource_uri=null _display_name=12.mp3 mime_type=audio/mpeg3 _data=/storage/emulated/0/Music/JDrop/1/1/12.mp3 title= group_id=1569 artist_id=322 is_pending=1 date_added=1579901253 album_id=2958 primary_directory=Music secondary_directory=JDrop bucket_id=687581593 media_type=2 relative_path=Music/JDrop/1/1/ from {P:30220;U:10165}
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE)
    at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
    at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:879)
    at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790)
    at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:88)
    at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1639)
    at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1494)
    at com.android.providers.media.MediaProvider.insertFile(MediaProvider.java:3050)
    at com.android.providers.media.MediaProvider.insertInternal(MediaProvider.java:3452)
    at com.android.providers.media.MediaProvider.insert(MediaProvider.java:3240)
    at android.content.ContentProvider$Transport.insert(ContentProvider.java:325)
    at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:164)
    at android.os.Binder.execTransactInternal(Binder.java:1032)
    at android.os.Binder.execTransact(Binder.java:1005)

所以 files._data 意味着文件已经存在于 MediaStore 中。 JDrop/1/1/12.mp3 处没有文件,它就在 MediaStore 中,我需要以某种方式删除它或为现有 MediaStore 条目获取 OutputStream 并相应地更新它。

我尝试使用以下代码在 MediaStore 中查询 ID 但没有成功。找出 IDURI 都可以。此外,从 SDK 29 开始,MediaStore.Audio.Media.DATA 已被弃用。所以我想在不使用它的情况下查询它。

if(uri == null) {
    val id: Long = ctx.contentResolver.query(
            collection,
            arrayOf(BaseColumns._ID),
            "${MediaStore.Audio.Media.RELATIVE_PATH}=? AND ${MediaStore.Audio.Media.DISPLAY_NAME}=?",
            arrayOf(libraryPart.rootFolderRelativePath, remoteLibraryEntry.getFilename()),
            null)?.use {
        if (it.moveToNext())
            it.getLong(it.getColumnIndex(BaseColumns._ID))
        else null
    } ?: return false

    uri = Uri.withAppendedPath(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id!!.toString())
}

编辑 1(查询 _data)

我现在还尝试使用我知道无法插入的硬编码路径查询 _data,但没有取得太大成功。

val id: Long = ctx.contentResolver.query(
            collection,
            arrayOf(BaseColumns._ID),
            "${MediaStore.Audio.Media.DATA}=?",
            arrayOf("/storage/emulated/0/Music/JDrop/1/1/12.mp3"),
            null)?.use {
        if (it.moveToNext())
            it.getLong(it.getColumnIndex(BaseColumns._ID))
        else null
    } ?: return false

也返回 null 和 returns false。

编辑 2(查询所有内容并查看它 returns)

我尝试按照建议对整个集合进行一些测试查询。

class TestQueryObject(val id: Long, val relativePath: String, val displayName: String)
val results = mutableListOf<TestQueryObject>()

ctx.contentResolver.query(
        collection,
        arrayOf(MediaStore.Audio.Media._ID, MediaStore.Audio.Media.RELATIVE_PATH, MediaStore.Audio.Media.DISPLAY_NAME),
        null,
        null,
        null)?.use {
    while (it.moveToNext()) {
        results.add(TestQueryObject(
                id = it.getLong(it.getColumnIndex(MediaStore.Audio.Media._ID)),
                relativePath = it.getString(it.getColumnIndex(MediaStore.Audio.Media.RELATIVE_PATH)),
                displayName = it.getString(it.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME))
        ))
    }
}

var find12 = results.find { it.displayName == "12.mp3" }

它 returns 包含 2557 个条目的列表。例如,第一个名称是 "5.mp3",id 是 79658,相对路径是 "Music/JDrop/1/1"。前面有一个我不知道的Music/。但是 find12 仍然是空的。

编辑 3(可能重要也可能不重要的其他想法)

可能还值得注意的是,它不会发生在我使用 android 10 创建的 Android 模拟器上。但它会发生在我的 OnePlus 6 上,其中包含大约 300 个文件和适用于所有其他人。我已经在 Android 9 上使用该程序,然后升级到 Android 10。我在某处读到,当您升级到 10 时,来自 Android 9 的文件可能被认为是孤立的,而不是属于您的应用程序(至少当您卸载创建它们的应用程序时会发生这种情况)。所以我可能无法再访问我正在寻找的媒体存储条目?不过,我也想过。现在,任何应用程序都可以在未经任何许可的情况下访问阅读媒体。因此,如果这是一个访问问题,则在尝试写入时应该会失败。不是在阅读或找到它时。

此外,如@CommonsWare 所述,IS_PENDING 仍可能设置为 1。我确实在我发布的代码下方将其设置回 0。但是,每当我在调试时关闭程序时,该代码可能永远不会执行,因为它会在下载和写入文件的部分执行 9/10,因为这需要 by far程序中发生任何事情的最长时间。

我现在想通了这个问题。感谢@CommonsWare 提及未决位。我可能花了 更长的时间 才弄明白。

有一种方法 Uri MediaStore.setIncludePending(Uri uri),你可以输入你的 uri,你可以用 返回一个 uri,你可以查询包含的未决项目。

使用这个新的 Uri,我从这个方法 returns 我的 find12 事情成功了!

val collection = MediaStore.Audio.Media
    .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
        
var uri = ctx.contentResolver.insert(collection, values)

if(uri == null) {

    class TestQueryObject(val id: Long, val relativePath: String, val displayName: String)
    val results = mutableListOf<TestQueryObject>()

    ctx.contentResolver.query(
            MediaStore.setIncludePending(collection),
            arrayOf(MediaStore.Audio.Media._ID, MediaStore.Audio.Media.RELATIVE_PATH, MediaStore.Audio.Media.DISPLAY_NAME),
            null,
            null,
            null)?.use {
        while (it.moveToNext()) {
            results.add(TestQueryObject(
                    id = it.getLong(it.getColumnIndex(MediaStore.Audio.Media._ID)),
                    relativePath = it.getString(it.getColumnIndex(MediaStore.Audio.Media.RELATIVE_PATH)),
                    displayName = it.getString(it.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME))
            ))
        }
    }

    var find12 = results.find { it.displayName == "12.mp3" }
}

现在我可以开始让它再次正常工作了。


编辑Android11

它出现在 Android11 MediaStore.setIncludePending(collection) 现已弃用,我们应该 use a different query method that accepts a bundle

所以就变成了这个样子

val queryCollection =
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
            MediaStore.setIncludePending(collection)
        else collection

val queryProjection = arrayOf(MediaStore.Audio.Media._ID)

val querySelection = "${MediaStore.Audio.Media.RELATIVE_PATH}=? AND ${MediaStore.Audio.Media.DISPLAY_NAME}=?"
val querySelectionArgs = arrayOf("someRelativePath", "someFilename")

val queryBundle = Bundle().apply {
    putString(ContentResolver.QUERY_ARG_SQL_SELECTION, querySelection)
    putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, querySelectionArgs)
}

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
     queryBundle.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_INCLUDE)

ctx.contentResolver.query(queryCollection, queryProjection, queryBundle, null)?.use {
    ... Do something
}

在 android 11 之前,无法使用捆绑包中包含待定项目的那些变量。

为 android 10 支持启用传统存储并像 android < 10 那样使用它并且只在 android 10 上实现分区存储可能是一个更好的主意。该选项在 android 11 上被忽略,但如果应用程序在 android 10.

上运行,它仍然有效