В моем приложении я использую 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 таким образом).