Медиа-сканер для вторичного хранения на Android Q - PullRequest
5 голосов
/ 09 мая 2019

С более новым Android Q многое изменилось, особенно с ограниченным объемом хранилища и постепенным устареванием file:/// URI.Проблема заключается в отсутствии документации о том, как правильно обрабатывать медиафайлы на устройствах Android Q.

У меня есть приложение для управления медиафайлами (аудио), и я пока не могу найти надежный способ сообщить ОС, чтоЯ внес изменение в файл, чтобы он мог обновить свою запись в 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

AudioFileContentValues - некоторые значения столбцов из MediaStore.Audio.AudioColumns.

Старый метод, основанный на file:// URI:

Uri uri = MediaStore.Audio.Media.getContentUriForPath(file_path);
newUri = context.getContentResolver().insert(uri, AudioFileContentValues);
  • MediaStore.Audio.Media.getContentUriForPath устарела
  • Все еще не работает

Более новый метод, основанный на том, что я мог бы собрать вместе from документация :

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 голосов
/ 20 июля 2019

Я также в настоящее время борюсь с этим.

Я думаю, что вы хотите сделать, вы не можете больше делать, когда вы находитесь на Android Q, потому что у вас нет доступаМузыкальный каталог на Q .Вам разрешено создавать файлы и обращаться к ним только в тех каталогах, которые вы создали.Вы не создали музыкальный каталог.

Теперь каждое изменение в Media должно произойти, бросил 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.

Я также теперь начал реализовывать вещи дважды с проверками версий Android.Это раздражает.Я не хочу это делать.Но, похоже, это единственный способ.

Я просто добавлю немного кода о том, как мне удалось вставить музыку на Android.Q и ниже.Это не идеально.Я должен указать тип MIME для Q, потому что флаки теперь каким-то образом станут .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 о распределенной / общей памяти https://youtu.be/3EtBw5s9iRY

Это, вероятно, не ответит на все ваши вопросы.Это достаточно точно не для меня.Но было полезно начать с приблизительного представления о том, что они даже изменили для начала.


Для удаления и обновления файлов то же самое в Q, если вы вызываете delete в записи медиастора,файл будет удален.Прежде, Q, вы должны также вручную удалить файл.Но если вы сделаете это на Q, ваше приложение упадет.Итак, снова вы должны проверить, есть ли у вас Q или более старая версия Android, и предпринять соответствующие действия.

...