Должен ли мой пользовательский DocumentsProvider использовать ParcelFileDescriptor для потоковой передачи выбранного файла? - PullRequest
0 голосов
/ 22 января 2019

В моем приложении я использую Glide для обработки / отображения изображений и ExoPlayer для отображения видео. Оба могут получить прямую URI-ссылку на файл и будут обрабатывать потоковое содержимое, буферизировать его, изменять его размер, управлять кэшем и т. Д. Другими словами, все сложные вещи!

Ключевым моментом здесь является формат этого URI и то, что происходит под обложками при использовании средства выбора файлов Android и пользовательских провайдеров документов.

Поскольку я хочу, чтобы пользователь выбирал изображения, видео и аудио для использования в приложении независимо от того, является ли нужный файл локальным для мобильного устройства или в Интернете, я решил использовать средство выбора файлов Android. Это означает, что я должен написать или иным образом предоставить экземпляр DocumentsProvider в соответствии с Storage Access Framework для каждого онлайн-ресурса, который еще не поддерживает SAF (то есть DropBox, Snapchat, Instagram и т. Д.).

Хотя это заняло немного времени, написание классов DocumentsProvider не слишком сложное.

Проблема начинается с того факта, что URI, возвращенный обратно из Chooser (и, следовательно, то, что я сохраняю как постоянный URI для последующего использования моим приложением), относится к разновидности content:// - это не http:// или https:// типа, с которым обычно работает Glide / ExoPlayer.

Так, например, предположим, что тип строки content:// используется в качестве аргумента, в конечном счете, для метода .load() ниже:

            GlideApp.with(context)
                    .load(Uri.parse(media.getPathToMedia()))
                    .apply(RequestOptions.fitCenterTransform())
                    .placeholder(placeHolder)
                    .error(R.drawable.ic_image_error)
                    .into(imageView);

Далее будет DocumentsProvider экземпляр, связанный с этим URI, в конечном итоге получит вызов openDocument(). Так, например, я написал собственный DropboxProvider. URI, возвращенный из Chooser, который я храню в базе данных моего приложения, выглядит примерно так:

content://authority_string/document/XXX-YYY-ZZZ.

При передаче этой строки в метод load() Glide вызывается мой метод openDocument() класса DropboxProvider, и там я анализирую идентификатор документа, входу в DropBox и использую их API для захвата файл локально и вернуть ParcelFileDescriptor обертывающий объект File, который указывает на этот загруженный файл. Затем вызывающая сторона может разрешить этот объект дескриптора соответствующим образом, и Glide загружает его правильно.

Все это отлично работает.

Однако меня поразило, что этот подход не является оптимальным по нескольким причинам, главная из которых заключается в том, что, хотя Glide / ExoPlayer не имеет ни малейшего понятия, как найти файл в DropBox, как только они имеют прямую ссылку на него, они поддерживают извлечение, буферизация, потоковая передача, управление кэшем и т. д., вероятно, намного лучше, чем я это делаю.

Мне кажется, что гораздо лучше придерживаться подхода «разделения интересов», а НЕ заниматься моим приложением, чтобы «владеть» обязанностями по загрузке / потоку и управлению кэшем. Просто возьмите на себя ответственность за «идентификацию файла».

Так что это действительно суть моего вопроса - где здесь лучшая "отрезанная" линия с точки зрения того, кто за что отвечает?

Я не могу уйти от того факта, что результатом выбора является content:// ссылка. Это просто, как построен Storage Access Framework.

Но я мог бы расширить свой провайдер, чтобы заранее идентифицировать эту прямую ссылку, и вернуть ее в курсоре, когда пользователь изначально выбирает файл. Затем, после завершения выбора файла, используйте стандартные методы Resolver (), чтобы записать эту информацию в мой Media объект, который я затем сохраню в своей БД:

public static void populateMediaFromUri(Context context, Media media, Uri uri) {
    *
    *
    *
    String path = null;

    cursor = context.getContentResolver().query(uri, null, null, null, null);
    try {
        if ( cursor != null && cursor.moveToFirst() ) {

            String directLink = cursor.getString(cursor.getColumnIndex(AbstractStorageProvider.COLUMN_DIRECT_LINK));
            if ( directLink == null ) {
                path = uri.toString();
            } else {
                path = directLink;
            }

            *
            *
            *
            media.setPathToMedia(path);

        }
    } catch (Exception e ) {
        Timber.d("Error resolving to path, requested was %s, error was %s", uri.getPath(), e.getMessage());
    } finally {
        if ( cursor != null ) {
            cursor.close();
        }
    }

Затем я могу использовать этот путь прямой ссылки для перехода к Glide или ExoPlayer вместо основанного на content:// URI, который я сейчас получаю от File Chooser. Это позволит Glide / ExoPlayer полностью обработать получение этих файлов (мой пользовательский провайдер больше не будет вызываться для разрешения получения файла).

Или я продолжаю использовать URI типа content:// в моих вызовах Glide / ExoPlayer и исследую, как создать ParcelFileDescriptor, который оборачивает канал в реальный файл и возвращает его из openDocument()? Похоже, на это намекают в качестве возможного подхода в прекрасной статье Иана в разделе Получение к сути документа: байты! .

Но, опять же, в этом случае, похоже, дублирование усилий, уже присутствующих в этих двух инструментах (пока я не смог найти примеры использования ParcelFileDescriptor таким образом).

...