Я ищу способ правильно поделиться (не ОТКРЫТЬ) внутренним файлом с внешним приложением с помощью 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
внутренне проверяет , является ли сам по себе идет на экспорт, если это так, то бросает исключение.
android
android-contentprovider
android-support-library
android-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); }
Альтернативный метод согласно документации :
Кстати. при необходимости вы можете скопировать источник FileProvider и изменить
attachInfo
метод, чтобы поставщик не проверял, экспортируется ли он.источник
grantUriPermission
метод, который нам нужен, мы должны предоставить имя пакета приложения, которому мы хотим предоставить разрешение. Однако при совместном использовании мы, как правило, не знаем, какое приложение предназначено для обмена.Пример полностью работающего кода, как поделиться файлом из внутренней папки приложения. Проверено на Android 7 и Android 5.
AndroidManifest.xml
</application> .... <provider android:name="androidx.core.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);
источник
ShareCompat.IntentBuilder
разрешения URI и чтения наконец сделало это за меня.FileProvider
что работает. В других ответах неправильно обрабатывается намерение (они не используютсяShareCompat.IntentBuilder
), и это намерение не может быть открыто внешними приложениями каким-либо значимым образом. Я потратил буквально большую часть дня на эту ерунду, пока наконец не нашел ваш ответ.Это решение работает у меня с OS 4.4. Чтобы он работал на всех устройствах, я добавил обходной путь для старых устройств. Это гарантирует, что всегда будет использоваться самое безопасное решение.
Manifest.xml:
<provider android:name="androidx.core.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 () Фрагмента или Действия.
источник
file
это не изменится сonResume
наonDestroy
?Поскольку, как Фил говорит в своем комментарии к исходному вопросу, это уникально, и в 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 Drive.
источник
Насколько я могу судить, это будет работать только на более новых версиях 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!"); }
источник
В моем приложении 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>
И поскольку мои файлы хранились в каталоге корневых файлов, файл paths.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(); } }
У меня это сработало. Надеюсь, это поможет :)
источник
Если вы получаете изображение с камеры, ни одно из этих решений не работает на 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); }
источник
просто для улучшения ответа, указанного выше: если вы получаете 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); }
источник
Хочу поделиться тем, что нас заблокировало на пару дней: код файлового провайдера ДОЛЖЕН быть вставлен между тегами приложения, а не после него. Это может быть банально, но это нигде не указано, и я подумал, что мог бы кому-то помочь! (еще раз спасибо piolo94)
источник