Как использовать поддержку FileProvider для обмена контентом с другими приложениями?
Я ищу способ правильно поделиться (не открывать) внутренний файл с внешним приложением с помощью библиотеки поддержки Android FileProvider.
следуя примеру в документах,
<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
extra (как было установлено setStream
).
Я попытался поставить под угрозу безопасность путем установки android:exported
до true
для провайдера, но FileProvider
внутренне проверяет, экспортируется ли сам, когда это так, он выдает исключение.
9 ответов:
используя
FileProvider
из библиотеки поддержки вы должны вручную предоставлять и отменять разрешения (во время выполнения) для других приложений для чтения определенного Uri. Используйте контексте.grantUriPermission и контексте.отзыв лицензии методы.например:
//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 содержимого в намерение, вызвав setData ().
- затем вызовите метод Intent.setFlags() с помощью FLAG_GRANT_READ_URI_PERMISSION или FLAG_GRANT_WRITE_URI_PERMISSION или обоих.
наконец, отправьте намерение в другое приложение. Чаще всего это делается путем вызова метода setResult().
разрешения, предоставленные в намерении, остаются в силе, пока стек приемная деятельность является активной. Когда стек заканчивается,
разрешения автоматически удаляются. Разрешения, предоставленные одному
Действия в клиентском приложении автоматически распространяются на другие
компоненты этого приложения.кстати. если вам нужно, вы можете скопировать источник FileProvider, и
attachInfo
метод для предотвращения проверки поставщика, если он экспортируется.
Это решение работает для меня с ОС 4.4. Чтобы заставить его работать на всех устройствах, я добавила обходной путь для старых устройств. Это гарантирует, что всегда используется самое безопасное решение.
Манифест.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>
Java:
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() фрагмента или действия.
полностью рабочий пример кода как поделиться файлом из внутренней папки приложения. Протестировано на 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);
поскольку, как говорит Фил в своем комментарии к исходному вопросу, это уникально, и в google нет другой информации о SO on, я подумал, что должен также поделиться своими результатами:
в моем приложении FileProvider работал из коробки, чтобы обмениваться файлами с помощью share intent. Не было никакой специальной конфигурации или кода, необходимых, кроме того, чтобы настроить FileProvider. В моем манифесте.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>
В моем коде I есть:
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 drive без каких-либо проблем.
насколько я могу сказать, это будет работать только на новых версиях Android, поэтому вам, вероятно, придется придумать другой способ сделать это. Это решение работает для меня на 4.4, но не на 4.0 или 2.3.3, поэтому это не будет полезным способом обмена контентом для приложения, которое предназначено для работы на любом устройстве Android.
в манифесте.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 и запустите намерение share, в этом случае действие называется 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!"); }
В моем приложении 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); }
Если вы получаете изображение с камеры ни одно из этих решений для Android 4.4. В этом случае лучше проверить версии.
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (intent.resolveActivity(getContext().getPackageManager()) != null) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { uri = Uri.fromFile(file); } else { uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file); } intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); startActivityForResult(intent, CAMERA_REQUEST); }