Как использовать поддержку FileProvider для обмена контентом с другими приложениями?

Я ищу способ правильно разделить (не OPEN) внутренний файл с внешним приложением, используя FileProvider из библиотеки поддержки Android.

Следуя примеру документов,

<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.android.supportv4.my_files" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/my_paths" /> </provider> 

И использование ShareCompat для совместного использования файла в других приложениях следующим образом:

 ShareCompat.IntentBuilder.from(activity) .setStream(uri) // uri from FileProvider .setType("text/html") .getIntent() .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 

Не работает, поскольку FLAG_GRANT_READ_URI_PERMISSION предоставляет только разрешение для Uri, указанное в data намерения, а не значение EXTRA_STREAM дополнительно (как было установлено setStream ).

Я попытался поставить под угрозу безопасность, установив android:exported в true для провайдера, но FileProvider внутренне проверяет, экспортирован ли он сам, когда это так, он выдает исключение.

Solutions Collecting From Web of "Как использовать поддержку FileProvider для обмена контентом с другими приложениями?"

Используя FileProvider из библиотеки поддержки, вы должны вручную предоставлять и отзывать разрешения (во время выполнения) для других приложений для чтения определенных Uri. Используйте методы Context.grantUriPermission и Context.revokeUriPermission .

Например:

 //grant permision for app with package "packegeName", eg. before starting other app via intent context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); //revoke permisions context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); 

В крайнем случае, если вы не можете предоставить имя пакета, вы можете предоставить разрешение всем приложениям, которые могут справиться с конкретным намерением:

 //grant permisions for all apps that can handle given intent Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); ... List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); } 

Альтернативный метод согласно документации :

  • Поместите URI содержимого в Intent, вызвав setData ().
  • Затем вызовите метод Intent.setFlags () с FLAG_GRANT_READ_URI_PERMISSION или FLAG_GRANT_WRITE_URI_PERMISSION или с обоими.
  • Наконец, отправьте намерение в другое приложение. Чаще всего вы делаете это, вызывая setResult ().

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

Btw. Если вам нужно, вы можете скопировать источник FileProvider и изменить метод attachInfo чтобы поставщик не мог проверить, экспортирован ли он.

Это решение работает для меня с OS 4.4. Чтобы он работал на всех устройствах, я добавил обходной путь для более старых устройств. Это гарантирует, что всегда используется самое безопасное решение.

manifest.xml:

  <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.package.name.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> 

file_paths.xml:

 <paths> <files-path name="app_directory" path="directory/"/> </paths> 

Ява:

 public static void sendFile(Context context) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); String dirpath = context.getFilesDir() + File.separator + "directory"; File file = new File(dirpath + File.separator + "file.txt"); Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file); intent.putExtra(Intent.EXTRA_STREAM, uri); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Workaround for Android bug. // grantUriPermission also needed for KITKAT, // see https://code.google.com/p/android/issues/detail?id=76683 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } if (intent.resolveActivity(context.getPackageManager()) != null) { context.startActivity(intent); } } public static void revokeFileReadPermission(Context context) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { String dirpath = context.getFilesDir() + File.separator + "directory"; File file = new File(dirpath + File.separator + "file.txt"); Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file); context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } 

Разрешение отменяется с помощью revokeFileReadPermission () в методах onResume и onDestroy () фрагмента или Activity.

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

В manifest.xml:

 <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.mydomain.myapp.SharingActivity" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> 

Обратите внимание на то, как вы указываете полномочия. Вы должны указать действие, из которого вы создадите URI, и запустить намерение общего доступа, в этом случае действие называется SharingActivity. Это требование не очевидно из документов Google!

file_paths.xml:

 <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="just_a_name" path=""/> </paths> 

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

В SharingActivity.java:

 Uri contentUri = FileProvider.getUriForFile(getActivity(), "com.mydomain.myapp.SharingActivity", myFile); Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("image/jpeg"); shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(Intent.createChooser(shareIntent, "Share with")); 

В этом примере мы используем изображение JPEG.

Наконец, вероятно, неплохо заверить себя, что вы правильно сохранили файл и что вы можете получить к нему доступ примерно так:

 File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg"); if(myFile != null){ Log.d(TAG, "File found, file description: "+myFile.toString()); }else{ Log.w(TAG, "File not found!"); } 

Поскольку, как говорит Фил в своем комментарии к первому вопросу, это уникально, и в Google нет другой информации о SO, я думал, что должен также поделиться своими результатами:

В моем приложении FileProvider работал из коробки, чтобы обмениваться файлами, используя намерение совместного использования. Не было никакой специальной конфигурации или кода, кроме этого, чтобы настроить FileProvider. В моем manifest.xml я разместил:

  <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.my.apps.package.files" android:exported="false" android:grantUriPermissions="true" > <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/my_paths" /> </provider> 

В my_paths.xml у меня есть:

 <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="files" path="." /> </paths> 

В моем коде у меня есть:

  Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("application/xml"); Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare); shareIntent.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file))); 

И я могу без проблем разобраться с хранилищем своих файлов в закрытом хранилище своих приложений с такими приложениями, как Gmail и Google.

Полностью рабочий пример кода, как обмениваться файлом из внутренней папки приложения. Протестировано на Android 7 и Android 5.

AndroidManifest.xml

 </application> .... <provider android:name="android.support.v4.content.FileProvider" android:authorities="android.getqardio.com.gmslocationtest" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application> 

XML / provider_paths

 <?xml version="1.0" encoding="utf-8"?> <paths> <files-path name="share" path="external_files"/> </paths> 

Сам код

  File imagePath = new File(getFilesDir(), "external_files"); imagePath.mkdir(); File imageFile = new File(imagePath.getPath(), "test.jpg"); // Write data in your file Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile); Intent intent = ShareCompat.IntentBuilder.from(this) .setStream(uri) // uri from FileProvider .setType("text/html") .getIntent() .setAction(Intent.ACTION_VIEW) //Change if needed .setDataAndType(uri, "image/*") .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent); 

В моем приложении FileProvider работает нормально, и я могу подключать внутренние файлы, хранящиеся в каталоге файлов, к почтовым клиентам, таким как Gmail, Yahoo и т. Д.

В моем манифесте, как упоминалось в документации по Android, я разместил:

 <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.package.name.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> 

И поскольку мои файлы были сохранены в корневом каталоге файлов, filepaths.xml были следующими:

  <paths> <files-path path="." name="name" /> 

Теперь в коде:

  File file=new File(context.getFilesDir(),"test.txt"); Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE); shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Test"); shareIntent.setType("text/plain"); shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] {"email-address you want to send the file to"}); Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider", file); ArrayList<Uri> uris = new ArrayList<Uri>(); uris.add(uri); shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); try { context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } catch(ActivityNotFoundException e) { Toast.makeText(context, "Sorry No email Application was found", Toast.LENGTH_SHORT).show(); } } 

Это сработало для меня. Надеюсь, это поможет 🙂

Просто чтобы улучшить ответ, указанный выше: если вы получаете NullPointerEx:

Вы также можете использовать getApplicationContext () без контекста

  List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); }