从 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
但没有成功。找出 ID
或 URI
都可以。此外,从 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.
上运行,它仍然有效
我正在尝试简单地添加音频文件。我的解决方案大多有效。但是它不起作用的地方是文件已经存在于 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
但没有成功。找出 ID
或 URI
都可以。此外,从 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.
上运行,它仍然有效