Объем хранилища: как удалить несколько аудиофайлов через MediaStore?

Пытаюсь удалить аудиофайлы с внешнего хранилища устройства (например в папке /storage/emulated/0/Music). Проанализировав MediaStore образец, я получил следующее решение для API 28 и ранее:

fun deleteTracks(trackIds: LongArray): Int {
    val whereClause = buildWildcardInClause(trackIds.size) // _id IN (?, ?, ?, ...)
    return resolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, Array(trackIds.size) { trackIds[it].toString() })
}

Я заметил, что приведенный выше код удаляет треки только из базы данных MediaStore, но не с устройства (файлы повторно добавляются в MediaStore после перезагрузки). Поэтому я изменил этот код, чтобы запросить столбец Media.DATA, а затем использовал эту информацию для удаления связанных файлов. Это работает как задумано.

Но теперь, когда Android Q представил Scoped Storage, Media.DATAAlbums.ALBUM_ART) устарели, потому что приложение может не иметь доступа к этим файлам. ContentResolver.openFileDescriptor можно использовать только для чтения файлов, но не для их удаления.

Тогда каков рекомендуемый способ удаления треков в Android Q? В примере не показано, как удалить несколько файлов из MediaStore, а MediaStore.Audio.Media, похоже, работает иначе, чем MediaStore.Images.Media.


person Thibault Seisel    schedule 08.10.2019    source источник
comment
(that were copied to the device from USB) from the external storage. Неясно, где эти файлы должны находиться после копирования. Вы имели ввиду ДО внешнее хранилище? Где именно? И от USB тоже непонятно, пока не объяснишь как именно.   -  person blackapps    schedule 08.10.2019
comment
Это файлы в формате mp3, которые были загружены в папку /storage/emulated/0/Music. Я хочу программно удалить их из своего приложения. Я отредактировал вопрос, чтобы было понятнее.   -  person Thibault Seisel    schedule 08.10.2019
comment
Если вы можете помещать файлы в /storage/emulated/0/Music, вы, безусловно, можете удалить их самостоятельно. Зачем возиться с MediaStore?   -  person blackapps    schedule 08.10.2019
comment
Поскольку Android 10 вводит новые ограничения на файлы, хранящиеся в общем хранилище. Приложения больше не смогут видеть все файлы: developer.android.com / обучение / хранилище данных / файлы /   -  person Thibault Seisel    schedule 08.10.2019
comment
Вы начинаете с чего-то, что здесь не имеет значения. Вы сказали, что можете помещать файлы в /storage/emulated/0/Music. Что ж, если это так, то вы «видели» эту папку и можете снова удалить эти файлы. Пожалуйста, дополните.   -  person blackapps    schedule 08.10.2019
comment
Я не создавал эти файлы программно в моем приложении; пользователь сделал это, подключив свой телефон к компьютеру. Таким образом, мое приложение не может видеть эту папку из-за ограничений хранилища с ограниченным объемом памяти.   -  person Thibault Seisel    schedule 08.10.2019
comment
Ваше приложение, особенно если оно предназначено для Android.Q, может видеть почти все на основном разделе и на карте microSD. И все может читать и писать. Просто используйте Storage Access Framework. Позвольте пользователю выбрать этот каталог с помощью ACTION_OPEN_DOCUMENT_TREE или отдельные файлы с помощью ACTION_OPEN_DOCUMENT.   -  person blackapps    schedule 08.10.2019


Ответы (2)


В Android 10 есть два способа сделать это, в зависимости от значения android:requestLegacyExternalStorage в AndroidManifest.xml файле приложения.

Вариант 1: объем хранилища включен (android:requestLegacyExternalStorage="false")

Если ваше приложение добавило содержимое для начала (с использованием contentResolver.insert), то метод выше (с использованием contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray)) должен работать. В Android 10 любым контентом, отправленным в магазин мультимедиа приложением, можно свободно управлять с помощью этого приложения.

Для контента, добавленного пользователем или принадлежащего другому приложению, процесс немного сложнее.

Чтобы удалить отдельный элемент из хранилища мультимедиа, не передавайте общий URI хранилища мультимедиа в delete (т. Е. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), а используйте конкретный URI элемента.

context.contentResolver.delete(
    audioContentUri,
    "${MediaStore.Audio.Media._ID} = ?",
    arrayOf(audioContentId)

(Я почти уверен, что здесь нет необходимости в предложении 'where', но для меня это имело смысл :)

На Android 10 это, скорее всего, выдаст RecoverableSecurityException, если ваш targetSdkVersion> = 29. Внутри этого исключения находится IntentSender, который может быть запущен вашим Activity, чтобы запросить у пользователя разрешение на его изменение или удаление. (Отправитель находится в recoverableSecurityException.userAction.actionIntent.intentSender)

Поскольку пользователь должен предоставить разрешение на каждый элемент, их невозможно удалить массово. (Или, вероятно, можно запросить разрешение на изменение или удаление всего альбома, по одной песне за раз, а затем использовать delete запрос, который вы использовали выше. В этот момент ваше приложение будет иметь разрешение на их удаление, и Media Store сделает все сразу. Но сначала нужно попросить каждую песню.)

Вариант 2: старое хранилище включено (android:requestLegacyExternalStorage="true")

В этом случае простой вызов contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray) не только удаляет элементы из базы данных, но также удаляет файлы.

Приложение должно иметь WRITE_EXTERNAL_STORAGE разрешение, чтобы это работало, но я проверил код, который вы указали в вопросе, и убедился, что файлы также были удалены.

Итак, для более старых версий API, я думаю, вам нужно использовать contentResolver.delete и самостоятельно отсоединить файл (или отсоединить файл и отправить запрос на его повторное сканирование через MediaScannerConnection).

Для Android 10+ contentResolver.delete удалит как индекс, так и само содержимое.

person Nicole Borrelli    schedule 10.10.2019
comment
Спасибо за подробный ответ. Однако я считаю, что запрашивать согласие пользователя на удаление каждой произвольной дорожки неудобно, особенно при удалении из MediaBrowserService (настраиваемое действие). Я подожду, пока Android 10 появится на моем телефоне, и посмотрю, как с этим справились Youtube Music и Google Files. - person Thibault Seisel; 11.10.2019
comment
Удаление по одному файлу за раз - это ужасно. Должен быть способ удалить их сразу. - person artman; 20.01.2020
comment
@artman Будет в следующей версии Android. Если ваше приложение часто выполняет массовые операции (удаление или изменение групп элементов мультимедиа), может быть лучше использовать android:requestLegacyExternalStorage на Android 10 и использовать операции массового редактирования / удаления в следующей версии. - person Nicole Borrelli; 29.01.2020
comment
Для Android 10+ contentResolver.delete удалит как индекс, так и сам xontent. Для меня это не так. Пожалуйста, посмотрите - person artman; 19.03.2020
comment
Чтобы было ясно, audioContentUri в этом случае извлекается через ContentUris.withAppendedId(Media.EXTERNAL_CONTENT_URI, songId). Кроме того, в моем тестировании это успешно удаляет песню из MediaStore, но основной файл остается. - person Tim Malseed; 03.04.2020
comment
@TimMalseed Нашли решение для этого? Также есть та же проблема, он просто удаляет запись в MediaStore. - person Vince VD; 07.04.2020
comment
@VinceVD нет. Единственная альтернатива - получение DocumentFile через Storage Access Framework (пользователю нужно будет выбрать каталог, содержащий файл, а затем вам нужно будет найти файл в этом дереве). Затем удаление DocumentFile. Сам я этого не делал, просто отключил опцию «удалить» при использовании MediaStore. - person Tim Malseed; 08.04.2020
comment
@NicoleBorrelli Scoped Storage настолько дыряв, что его нельзя использовать. чтобы удалить файл, вам нужно проверить исключение безопасности и отправить намерение. Например, я не нахожу startIntentSenderForResult в контексте службы. Все должно быть просто, а не безумно. - person AndrewBloom; 14.04.2020
comment
contentResolver.delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, MediaStore.Video.Media._ID + "=?", ids.toTypedArray()) не работает на Android 11 - person user25; 20.11.2020
comment
Я добавил android: requestLegacyExternalStorage = true в свой AndroidManifest, но все еще получаю RecoverableSecurityException на Android 29 (Q). Любое решение? - person Himanshu; 12.02.2021
comment
Я совершил ошибку, подумав, что android:requestLegacyExternalStorage вошел в тег uses-permission для WRITE_EXTERNAL_STORAGE, и не подумал дважды, даже после того, как натолкнулся на некоторую косвенно противоречивую информацию. Только когда я увидел это целых 2 дня спустя я понял, что это идет в теге application, который наконец-то заставил его работать в Android 10. В свою защиту я также работал над тем, чтобы в то время работать с удалением на других версиях Android. - person Pilot_51; 13.04.2021

В Android 11 удаление / обновление нескольких файлов мультимедиа поддерживается более совершенным и чистым API.

До Android 10 мы должны удалить физическую копию файла, сформировав объект File, а также удалить проиндексированный файл в MediaStore с помощью ContentResolver.delete () (или) выполнить сканирование мультимедиа в удаленном файле. который удалит его запись в MediaStore.

Вот как он работал в ОС Android 10 ниже. И все равно будет работать так же в Android 10, если вы отключили хранилище с ограниченным объемом, указав его в манифесте android: requestLegacyExternalStorage = true

Теперь в Android 11 вы вынуждены использовать ограниченное хранилище. Если вы хотите удалить любой медиафайл, созданный не вами, вы должны получить разрешение от пользователя. Вы можете получить разрешение с помощью MediaStore.createDeleteRequest (). Появится диалоговое окно с описанием того, какую операцию собираются выполнить пользователи. После предоставления разрешения у Android есть внутренний код, который позаботится об удалении как физического файла, так и записи в MediaStore.

private void requestDeletePermission(List<Uri> uriList){
  if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
      PendingIntent pi = MediaStore.createDeleteRequest(mActivity.getContentResolver(), uriList);

      try {
         startIntentSenderForResult(pi.getIntentSender(), REQUEST_PERM_DELETE, null, 0, 0,
                  0);
      } catch (SendIntentException e) { }
    }
}

Приведенный выше код будет делать и то, и другое, запрашивая разрешение на удаление, и после предоставления разрешения также удаляет файлы.

И результат вы получите в onActivityResult ()

person Velu    schedule 12.11.2020