Делимся файлами в Android с помощью FileProvider

Несколько недель назад мне было поручено открыть внутренний PDF-файл в любом приложении для чтения PDF на Android устройстве. Я думал, что это будет просто, но все оказалось сложнее. Документация Google по FileProvider оказалась запутанной и не имела конкретных примеров. Тем не менее, я знал, что должен использовать ContentProvider для решения этой проблемы.

Как один из примеров использования FileProvider, вы можете посмотреть следующий урок.

Что такое FileProvider?

ContentProvider — это компонент Android, который инкапсулирует данные и предоставляет их другим приложениям. Это необходимо в случае, если вам нужно обмениваться данными между несколькими приложениями. Например, данные контактов совместно используются другими приложениями, используя ContactsProvider, который является подклассом ContentProvider.

FileProvider — это подкласс ContentProvider. Хотя ContentProvider — это компонент, который позволяет вам безопасно делиться любыми данными, FileProvider используется специально для совместного использования внутренних файлов приложения. Класс FileProvider является частью библиотеки поддержки v4, поэтому обязательно включите ее в свой проект.

Чтобы заставить FileProvider работать, нужно выполнить следующие три действия:

  • Определить FileProvider в файле AndroidManifest.xml;
  • Создать XML-файл, содержащий все пути, которые FileProvider будет использовать совместно с другими приложениями;
  • Связать действительный URI в Intent и активировать его.

Определение FileProvider

Чтобы определить FileProvider внутри AndroidManifest.xml, вам необходимо ознакомиться с этими атрибутами и элементами:

  • android:authorities
  • android:exported
  • android:grantUriPermissions
  • android:name
  • <meta-data> субэлемент

Если всё это вам уже знакомо, ваше изучение FileProvider пройдёт немного легче, иначе я подготовил подробное описание каждого атрибута и его цели.

android:authorities

Вы должны определить хотя бы одно уникальное полномочие. Система Android хранит список всех поставщиков, и она отличает их по полномочиям. Полномочие определяет FileProvider точно так же, как ID приложения определяет приложение для Android.

В общем, Android-система использует определенную схему URI для ContentProviders. Схема определяется как content://<authority>/<path>, поэтому система будет знать, какой ContentProvider запрашивается, сопоставляя полномочия URI с полномочиями ContentProvider.

android:exported

Этим атрибутом можно легко злоупотребить, потому что его имя вводит в заблуждение. Чтобы понять смысл этого атрибута, подумайте о своем FileProvider как о комнате с запертыми дверями. Если вы установите значение true, вы в основном откроете свои двери для всех. Все будет работать как вы и программировали, но вы только что создали огромную проблему безопасности, так как каждое другое приложение сможет использовать ваш FileProvider без получения разрешения.

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

android:grantUriPermissions

Если мы продолжим думать о FileProvider как о запертой комнате, то этот атрибут используется для предоставления временного одноразового ключа для внешнего приложения. Этот атрибут позволяет безопасно обмениваться внутренним хранилищем вашего приложения. Все, что вам нужно сделать, это добавить FLAG_GRANT_READ_URI_PERMISSION или FLAG_GRANT_WRITE_URI_PERMISSION в намерение, которое активирует компонент для открытия внутреннего файла вашего приложения. Чтобы использовать эти флаги, установите для них значение true.

Элемент <provider> может также иметь субэлементы <grant-uri-permission>. Единственное различие заключается в том, что с помощью атрибута вы можете поделиться чем-либо внутри внутреннего хранилища вашего приложения, в то время как субэлементы позволяют вам выбрать конкретное подмножество данных для совместного использования. Чтобы вместо этого использовать субэлементы, установите значение false.

<meta-data> субэлемент

Этот субэлемент должен быть определен при использовании FileProvider. Вы должны определить путь к XML-файлу, который содержит все пути данных, которые ваш FileProvider может использовать с внешними приложениями.

XML-файл должен иметь элемент <paths> в качестве его корня. Элемент <paths> должен иметь как минимум один дочерний элемент, который может быть следующим:

  • <files-path/> — внутреннее хранилище приложения, Context#getFilesDir()
  • <cache-path/> — кэш внутреннего хранилища приложения, Context#getCacheDir()
  • <external-path/> — публичное внешнее хранилище, Environment.getExternalStorageDirectory()
  • <external-files-path/> — внешнее хранилище приложения, Context#getExternalFilesDir(null)
  • <external-cache-path/> — кэш внешнего хранилища приложения, Context#getExternalCacheDir()

Возможно, вы заметили, что они различаются в зависимости от каталога приложения, которое они определяют.

Каждый элемент должен иметь атрибуты path и name. Атрибут path определяет подкаталог, который вы хотите использовать, и он не поддерживает подстановочные знаки. Атрибут name используется по соображениям безопасности, и он заменит ваше имя подкаталога на его значение.

android:name

С помощью этого атрибута мы устанавливаем это значение для android.support.v4.content.FileProvider.

После того, как вы определили FileProvider в своем AndroidManifest.xml, вы, наконец, готовы его использовать. Чтобы поделиться файлом, вам нужно создать намерение и предоставить ему действительный URI. URI генерируется с использованием класса FileProvider.

Реализация кода

androidmanifest.xml

<provider
  android:name="android.support.v4.content.FileProvider"
  android:grantUriPermissions="true"
  android:exported="false"
  android:authorities="${applicationId}">

  <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_provider_paths"/>
</provider>

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

file_provider_paths.xml

<paths>
  <cache-path name="cache" path="/" />
  <files-path name=”files” path=”/” />
</paths>

Определяя пути, подобные этому, я разрешаю FileProvider делиться всеми файлами, находящимися внутри внутреннего кэша и каталога файлов приложения.

Использование вашего FileProvider

// создаём новое намерение
Intent intent = new Intent(Intent.ACTION_VIEW);

// устанавливаем флаг для того, чтобы дать внешнему приложению пользоваться нашим FileProvider
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

// генерируем URI, я определил полномочие как ID приложения в манифесте, последний параметр это файл, который я хочу открыть
String uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);

// я открываю PDF-файл, поэтому я даю ему действительный тип MIME
intent.setDataAndType(uri, "application/pdf");

// подтвердите, что устройство может открыть этот файл!
PackageManager pm = getActivity().getPackageManager();
if (intent.resolveActivity(pm) != null) {
startActivity(intent);
}

Как только вы поймете, как это работает, реализация вашего собственного FileProvider станет действительно простой.

Сложность проблемы заключается не в самом коде, а в документации и понимании того, как все взаимосвязано. Я надеюсь, что эта статья поможет вам реализовать FileProvider для вашего собственного использования и упростить чтение официальной документации.

Share on VKShare on Facebook1Share on Google+0Tweet about this on TwitterShare on LinkedIn0Share on Tumblr0Email this to someoneShare on Reddit0
Нашли ошибку в тексте?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *