Поскольку становится всё более очевидным, что схема file прекращает своё существование, давайте рассмотрим, как всё это будет работать сейчас.
Посмотрим на такую ситуацию со стороны потребителя: вам передают Uri с каким-нибудь контентом в случае, когда раньше вы ожидали путь к файлу. Как вы будете получать этот контент?
Что можно делать?
Во-первых, убедитесь, что в любом месте, где у вас есть <intent-filter> для схемы file, у вас также есть <intent-filter> для content, с областью видимости для соответствующего MIME типа. Вы можете получить контент Uri другими способами (например, в onActivityResult()), но если вы поддерживаете файл-подобный контент для таких действий, как ACTION_VIEW, убедитесь, что вы поддерживаете как file, так и content Uri.
Следующим шагом поймите, какую роль играет Uri. Некоторые значения Uri указывают на записи базы данных, например, если вы предлагаете пользователю выбрать контакт. Такие случаи в данный момент нам неинтересны. Другие значения указывают на вещи, которые могут какими-то файлами — вот для этого мы и нужна эта статья.
Затем, если Uri имеет схему content, вы можете сделать следующее с экземпляром ContentResolver:
- Вызовите openInputStream() для чтения контента.
- Вызовите getType(), чтобы получить тип MIME данных.
- Вызовите query(), запрашивающий OpenableColumns, в котором вы сможете получить размер контента и некоторую разновидность отображаемого имени, связанного с контентом.
Первые два пункта напоминают, как вы обрабатываете http или https Uri, и поэтому, если у вас есть написанный для этого код, вы можете переписать его, чтобы избежать дублирования кода.
И это, в общем-то, всё.
Чего не стоит делать?
К сожалению, хотя и не всем нравится эти решение, но многие думают, что это единственный подходящий вариант.
Просто забрать путь
Некоторые разработчики вызывают getPath() у Uri, а затем пытаются открыть его как файл (например, new File(uri.getPath())).
Не делайте так!
Этот способ никогда не был надёжным, так как он предполагает схему file и то, что у вас есть прямой доступ к местоположению файла с заданным путём.
Если у Uri есть какая-либо другая схема, например content, путь в значительной мере становится бесполезным для вас.
Попытка выбрать какие-либо отдельные части из Uri и работать с ними вряд ли окажется успешной для вас.
Представить, что MediaStore знает, чего вы хотите
Некоторые разработчики пытаются запросить MediaStore для столбца DATA в надежде, что они смогут конвертировать Uri в путь к файлу.
Не делайте так!
Не каждый content Uri поступает из MediaStore. На самом деле, в настоящее время относительно немногие Uri так делают. MediaStore ничего не знает о значениях Uri от других поставщиков. Кроме того, даже если вы получите MediaStore Uri, который может индексировать медиа на съёмном носителе, у вас не будет доступа к файловой системе.
Попробовать извлечь путь
Некоторые разработчики используют более сложный вариант для вышеупомянутых подходов, с двумя тоннами кода, чтобы посмотреть на полномочия Uri и попытаться получить путь, основанный на эвристике, разработанной для разных полномочий.
Не делайте так!
Тот факт, что это возможно, является частичной ошибкой Google, поскольку они не сделали значения Uri более скрытыми.
Однако, этот способ тоже ненадёжен:
- Приложения могут менять свои структуры Uri, и поэтому эвристика, которая работает сегодня, может не работать завтра.
- Возможно, у вас не будет доступа к файлу, даже если вы определите путь.
- Скоро случится Кембрийский взрыв приложений, публикующих собственный контент, используя своих собственных поставщиков и значения Uri, благодаря запрету на схему file. Вам необходимо поддерживать этих поставщиков, поэтому вам необходимо использовать метод openInputStream() в любом случае.
FAQ
Ниже представлен список вопрос, которые могут возникнуть в данной ситуации.
Как получить имя файла?
Вы не можете этого сделать.
Вы можете посмотреть последний сегмент Uri. Это может быть имя файла, а может и не быть. Нет требования, чтобы content Uri использовал реальное имя файла в качестве последнего сегмента пути.
DISPLAY_NAME, который вы можете получить из вышеупомянутого OpenableColumns, может быть именем файла, но также может и не быть. Термин «отображаемое имя» необязательно означает имя файла.
Кроме того, в зависимости от того, откуда контент идёт, фактически никакого файла может не существовать. Предположим. что кто-то в заметку с длинной записью в приложении для заметок, и это приложение делает её доступной через Uri для доступа к другим приложениям. Пользователь не создавал файл, не загружал, не делал ничего, связанного с файлами, с этой заметкой. Даже если для неё есть подлинное имя файла, оно не будет иметь никакого значения для пользователя.
Хорошо, тогда как мне получить расширение файла?
Вы не можете этого сделать.
Ещё раз, вы можете узнать, заканчивается ли Uri тем, что выглядит как имя файла. Нет никакого гарантии, что что-то вроде foo.bar будет являться файлом с расширением .bar.
Вы можете использовать MimeTypeMap или аналогичные виды преобразователей, чтобы попытаться получить MIME тип, который вы получаете из getType(), и затем получить расширение файла. Однако эти конвертеры обрабатывают только популярные MIME типы и не могут обработать кастомные.
Что делать, если мне нужно передать файл в библиотеку?
Во-первых, проверьте библиотеку на наличие в ней варианта InputStream с методом для файла, который вы хотите использовать. Если он есть, используйте InputStream.
Затем, рассмотрите замену библиотеку на ту, которая имеет вариант InputStream.
Наконец, создайте локальную копию контента, получив InputStream из ContentResolver, подучив FileOutputStream в локальном файле (например, внутри getCacheDir() для внутреннего хранилища) и используя операции ввода-вывода для копирования из InputStream в OutputStream. flush(), getFD.sync() и close() FileOutputStream, и теперь у вас есть файл. который вы можете передать в библиотеку.
Конечно, это копия контента, поэтому она может стать устаревшей. Этот способ хорош для одноразовой операции и подходит для случаев, когда вам законным образом нужна локальная копия файла.
Как долго я могу использовать Uri?
Недолго.
Предполагая, что на стороне провайдера ничего не происходит, Uri должен быть действителен в течение всего процесса, но после этого он может стать недействительным.
Представьте, что Uri является глубокой ссылкой на какой-нибудь контент, требующий аутентификации. URL, который вы получаете, является рабочим, поскольку пользователь прошёл аутентификацию. URL может быть полезен даже некоторое время, пока сеанс активен. Но в итоге сеанс завершится и URL станет бесполезным.
Но что делать, если мне понадобится контент на более длительное время?
Вы можете вызвать takePersistableUriPermission() в ContentResolver. Если провайдер предлагает удержать разрешения, и вы их принимаете, то система запомнит, что у вас есть доступ к Uri. Соблюдайте осторожность, вы можете использовать метод для неограниченного доступа к контенту бескончно или до тех пор, пока пользователь не переместит, удалит или сделает недоступным к контенту по старому Uri.
Проблема в том, что takePersistableUriPermission() не говорит вам, были ли получены разрешения. Вы должен будете вызвать getPersistedUriPermissions() и посмотреть, есть ли у вас разрешения. Возможно, здесь нет разрешений, которые можно удержать.
Другой вариант — сделать локальную копию, как было отмечено ранее в FAQ. Таким образом вы сможете контролировать свою копию. Это может потребовать некоторой корректировки вашего UI и терминологии. Вместо использования глаголов типа «перейти» (предполагая, что контент находится в другом месте), используйте «импортировать», «копировать», чтобы подчеркнуть тот факт, что содержимое копируется. Вы можете предложить функции «обновить», «заменить», чтобы пользователь смог получить свежий Uri, который вы можете использовать, чтобы заменить существующую локальную копию новой.
Зачем столько боли?
Видение Google заключается в том, что значения content Uri являются эквивалентом Android http / https в веб-приложениях: универсально понятные и используемые. В конце концов, ContentResolver может сделать этот контент доступным из:
- файлов, в том числе тех, к которым другие люди не имеют доступа.
- столбцов BLOB в базе данных.
- зашифрованных материалов, которые дешифруются на лету.
- материалов, которые хранятся в сети и должны быть загружены и сохранены в кеш.
- материалов, которые генерируются на лету.
Использование значений content Uri намного более гибкое и предлагает больше различных параметров безопасности.