андроид.ОС.FileUriExposedException: file:///storage / emulated / 0 / test.txt подвергается за пределами приложения через намерение.метод GetData()
приложение падает, когда я пытаюсь открыть файл. Он работает ниже Android Nougat, но на Android Nougat он падает. Он только вылетает, когда я пытаюсь открыть файл с SD-карты, а не из системного раздела. Некоторые проблемы с разрешением?
пример кода:
File file = new File("/storage/emulated/0/test.txt");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "text/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // Crashes on this line
Log:
android.ОС.FileUriExposedException: файл: / / / storage / emulated/0 / test.txt подвергается за пределами приложения через Намерение.getData ()
Edit:
при таргетинге на Android Nougat,file://
URI больше не разрешены. Мы должны использовать content://
URIs вместо этого. Однако мое приложение должно открывать файлы в корневых каталогах. Есть идеи?
16 ответов:
если
targetSdkVersion >= 24
, тогда мы должны использоватьFileProvider
класс, чтобы дать доступ к определенному файлу или папке, чтобы сделать их доступными для других приложений. Мы создаем свой собственный класс, наследующийFileProvider
чтобы убедиться, что наш FileProvider не конфликтует с FileProviders, объявленными в импортированных зависимостях, как описано здесь.шаги для замены
file://
URI сcontent://
URI:
добавить расширение класса
FileProvider
public class GenericFileProvider extends FileProvider {}
добавить тег FileProvider в AndroidManifest.XML-код под тег. Указать уникальные полномочия для
android:authorities
атрибут чтобы избежать конфликтов, импортированные зависимости могут указывать${applicationId}.provider
и другие широко используемые авторитеты.<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <application ... <provider android:name=".GenericFileProvider" android:authorities="${applicationId}.my.package.name.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application> </manifest>
- затем создать на . Папка может потребоваться для создания, если она не существует. Содержимое файла показано ниже. Он описывает, что мы хотел бы поделиться доступом к внешнему хранилищу в корневой папке
(path=".")
на имя external_files.<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths>
последний шаг-изменить строку кода ниже в
Uri photoURI = Uri.fromFile(createImageFile());
до
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
Edit: если вы используете намерение заставить систему открыть ваш файл, вам может потребоваться добавить следующую строку код:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
смотрите, пожалуйста, полный код и решение было объяснено здесь.
кроме решения с помощью
FileProvider
, есть еще один способ обойти это. Проще говоряStrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
на
Application.onCreate()
. Таким образом, виртуальная машина игнорирует файлURI
экспозицию.метод
builder.detectFileUriExposure()
включает проверку экспозиции файла, которая также является поведением по умолчанию, если мы не устанавливаем VmPolicy.
я столкнулся с проблемой, что если я использую
content://
URI
чтобы отправить что-то, некоторые приложения просто не могут этого понять. И понижениеtarget SDK
версия не допускается. В этом случае мое решение полезно.обновление:
как уже упоминалось в комментарии, StrictMode является диагностическим инструментом и не должен использоваться для этой проблемы. Когда я опубликовал этот ответ год назад, многие приложения могут получать только URI файлов. Они просто разбиваются, когда я пытался отправить им uri FileProvider. Теперь это исправлено в большинстве приложений, поэтому мы должны пойти с решением FileProvider.
Если ваше приложение нацелено на API 24+, и вы все еще хотите / должны использовать file: / / intents, вы можете использовать hacky способ отключить проверку времени выполнения:
if(Build.VERSION.SDK_INT>=24){ try{ Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure"); m.invoke(null); }catch(Exception e){ e.printStackTrace(); } }
метод
StrictMode.disableDeathOnFileUriExposure
скрыто и задокументировано как:/** * Used by lame internal apps that haven't done the hard work to get * themselves off file:// Uris yet. */
проблема в том, что мое приложение не хромает, а скорее не хочет быть искалеченным с помощью content:// intents, которые не поняты многими приложениями там. Например, открытие mp3-файла с содержимым: / / scheme предлагает гораздо меньше приложений, чем при открытии же более файл: схема//. Я не хочу платить за ошибки дизайна Google ограничивает функциональность моего приложения.
Google хочет, чтобы разработчики использовали схему контента, но система не готова к этому, в течение многих лет приложения были сделаны для использования файлов не "контент", файлы могут быть отредактированы и сохранены обратно, в то время как файлы, обслуживаемые по схеме контента, не могут быть (могут ли они?).
если
targetSdkVersion
24 или выше,вы не можете использоватьfile:
Uri
значенияIntents
на Android 7.0 + устройств.ваш выбор:
брось
targetSdkVersion
в 23 или ниже, илипоместите свой контент на внутреннюю память, а затем использовать
FileProvider
чтобы сделать его доступным выборочно для других приложенийнапример:
Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f)); i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(i);
(от этот пример проекта)
если
targetSdkVersion
выше 24, потом FileProvider используется для предоставления доступа.создать xml-файл (путь: res\xml)provider_paths.xml
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths>
добавить провайдер на AndroidManifest.xml
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider>
и заменить
Uri uri = Uri.fromFile(fileImagePath);
до
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
и вы хорошо идти. Надеюсь на это помогает.
сначала вам нужно добавить поставщика к вашему AndroidManifest
<application ...> <activity> .... </activity> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.your.package.fileProvider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
Теперь создайте файл в папке ресурсов xml (если вы используете android studio, вы можете нажать Alt + Enter после выделения file_paths и выбрать опцию Создать ресурс xml)
далее в файле file_paths введите
<?xml version="1.0" encoding="utf-8"?> <paths> <external-path path="Android/data/com.your.package/" name="files_root" /> <external-path path="." name="external_storage_root" /> </paths>
этот пример для внешнего пути вы можете ссылаться здесь дополнительные параметры. Это позволит вам обмениваться файлами, которые находятся в этой папке и ее подпапка.
Теперь все, что осталось, это создать намерение следующим образом:
MimeTypeMap mime = MimeTypeMap.getSingleton(); String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1); String type = mime.getMimeTypeFromExtension(ext); try { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile); intent.setDataAndType(contentUri, type); } else { intent.setDataAndType(Uri.fromFile(newFile), type); } startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT); } catch (ActivityNotFoundException anfe) { Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show(); }
EDIT: я добавил корневую папку sd-карты в file_paths. Я тестировал этот код и он работает.
@palash k ответ правильный и работал для внутренних файлов хранения, но в моем случае я хочу, чтобы открыть файлы из внешнего хранилища также, мое приложение разбился при открытии файла из внешнего хранилища, как sdcard и usb, но мне удается решить эту проблему путем изменения provider_paths.xml из принятого ответа
изменить provider_paths.xml ниже
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path path="Android/data/${applicationId}/" name="files_root" /> <root-path name="root" path="/" /> </paths>
и в классе java(никаких изменений, так как принятый ответ просто небольшой редактирование)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
Это поможет мне исправить сбой для файлов из внешних хранилищ, надеюсь, что это поможет кому-то с такой же проблемой, как у меня :)
использование fileProvider-это путь. Но вы можете использовать этот простой обходной путь:
предупреждение: это будет исправлено в следующем выпуске Android - https://issuetracker.google.com/issues/37122890#comment4
заменить:
startActivity(intent);
by
startActivity(Intent.createChooser(intent, "Your title"));
я использовал ответ Палаша, приведенный выше, но он был несколько неполным, я должен был предоставить такое разрешение
Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path)); List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); } }else { uri = Uri.fromFile(new File(path)); } intent.setDataAndType(uri, "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
просто вставьте приведенный ниже код в activity onCreate ()
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
он будет игнорировать экспозицию URI
Я не знаю, почему, я сделал все точно так же, как Pkosta (https://stackoverflow.com/a/38858040) но продолжал получать ошибку:
java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted
Я потратил часы на этот вопрос. Виновник? Котлин.
val playIntent = Intent(Intent.ACTION_VIEW, uri) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent
на самом деле установкаgetIntent().addFlags
вместо того, чтобы работать на моем недавно объявленном playIntent.
для загрузки pdf с сервера , добавьте ниже код в свой класс обслуживания. Надеюсь, что это полезно для вас.
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf"); intent = new Intent(Intent.ACTION_VIEW); //Log.e("pathOpen", file.getPath()); Uri contentUri; contentUri = Uri.fromFile(file); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= 24) { Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file); intent.setDataAndType(apkURI, "application/pdf"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } else { intent.setDataAndType(contentUri, "application/pdf"); }
и да, не забудьте добавить разрешения и поставщика в манифесте.
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider> </application>
просто вставьте приведенный ниже код в activity onCreate ()
StrictMode.VmPolicy.Builder builder = новый StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());
Он будет игнорировать воздействие URI
удачи в кодировании :-)
https://stackoverflow.com/a/38858040/395097 этот ответ является полным.
этот ответ Для - у вас уже есть приложение, которое было нацелено ниже 24, и теперь вы обновляетесь до targetSDKVersion >= 24.
В Android N изменяется только uri файла, открытый для стороннего приложения. (Не так, как мы использовали его раньше). Так что измените только те места, где вы разделяете путь с сторонним приложением (Камера в моем случае)
в нашем приложении мы были отправка uri в приложение камеры, в этом месте мы ожидаем, что приложение камеры будет хранить захваченное изображение.
- для android N мы создаем новый контент: / / URL на основе uri, указывающий на файл.
- мы генерируем обычный путь к файлу на основе api для того же самого (используя более старый метод).
теперь у нас есть 2 разных uri для одного и того же файла. #1 является общим с приложением камеры. Если цель камеры-успех, мы можем получить доступ к изображению из #2.
надеюсь, что это помогает.
Xamarin.Android
Примечание: путь xml / provider_paths.xml (.axml) не может быть решена, даже после создания xml под ресурсы (может быть, он может быть помещен в существующем месте, как значения, не пробовал), поэтому я прибегнул к этому, который работает сейчас. Тестирование показало, что его нужно вызывать только один раз за запуск приложения (что имеет смысл в том, что он изменяет рабочее состояние приложения). хост VM).
Примечание: xml должен быть заглавной буквой, так что Resources / Xml / provider_paths.xml
Java.Lang.ClassLoader cl = _this.Context.ClassLoader; Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode"); System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode"); var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V"); JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);