Делимся файлами в 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 для вашего собственного использования и упростить чтение официальной документации.

Делимся файлами в Android с помощью FileProvider: 9 комментариев

  1. Евгений

    android:name=»android.support.v4.content.FileProvider»
    почему в моем провайдере подсвечивается красным v4.content.FileProvider

  2. Volodya

    String uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);

    возвращаемый тип данных Uri, а не String
    Uri uri = FileProvider…

  3. Эмиль

    Спасибо!!!
    Я читал StackOverflow и документацию, но так до конца и не понял. Здесь автор написал все очень просто и понятно. Я споткнулся на files-path, а здесь это объяснено.

  4. Степан

    Не получается через fileProvider сделать фотографию. Т.е. нужно что бы приложенье зппустило камеру передав в него ссылку на доступный фаил. Не понимаю как сделать. Не могли бы вы показать пример как работать с камерой черех fileprovider. Заранее благодарю

    1. Аноним

      Я сейчас над тем же бъюсь и никак не могу реализовать подобное. Я тоже хочу чтобы камера открывалась, сохраняла во внутреннее хранилище фото и всё…
      Камера открылась, а код на линии с FileProvider.getUriForFile() умирает((

  5. александр

    У меня вопрос.
    Должен ли я использовать FileProvider если хочу передать только 1 изображение с Intent’oм, ?

    которые вроде бы уже поддерживает возможность передачи потока байт ()
    intent.putExtra(Intent.EXTRA_STREAM, uriOfImage);
    и нужно только указать путь к этой картинке

    Подробнее:
    я разрабатываю приложение.
    Хочу, чтобы по клику на одной из кнопок открывалось стандартное окно с выбором метода отправки (на e-mail, watsup, … ) и ОТПРАВЛЯЛСЯ ФАЙЛ С ИЗОБРАЖЕНИЕМ, которое лежит в ресурсах приложения в папке Drawable.

    Код, указанный ниже, высылает файл, но в WatsUp’e он определяеся как null.bin (хотя имеет правильный размер 2.5Mb. Это размер передаваемой картинки).

    —————————-код в java ——————————————

    public void onClick_sendMyFile(View view) {
    int imageID = R.drawable.my_image;
    Resources res = getResources();
    Uri uriOfImage = new Uri.Builder()
    .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
    .authority(res.getResourcePackageName(imageID))
    .appendPath(res.getResourceTypeName(imageID))
    .appendPath(res.getResourceEntryName(imageID))
    .build();

    tv_path.setText(uriOfImage.toString());

    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.putExtra(Intent.EXTRA_STREAM, uriOfImage);
    intent.setType(«image/*»);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    Intent intent1 = Intent.createChooser(intent, «чем выслать»);
    startActivity(intent1);

    }

Добавить комментарий для dimonbugor Отменить ответ

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