андроид.ОС.FileUriExposedException: файл: / / / хранилище / эмулированный/0 / тест.txt подвергается за пределами приложения через намерение.метод GetData()
приложение сбой, когда я пытаюсь открыть файл. Он работает ниже Android нуга, но на Android нуга он падает. Это происходит только при попытке открыть файл с 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: файл: / / / хранение / эмуляция / 0 / тест.txt подвергается за пределами приложения через Намерение.getData ()
Edit:
при таргетинге на Android нуга,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());
to
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 является диагностическим инструментом и не должен использоваться для этой проблемы. Когда я опубликовал этот ответ год назад, многие приложения могут получать только файл uris. Они просто разбиваются, когда я пытался отправить им 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);
to
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();
}
редактировать: я добавил корневую папку 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(без изменений, так как принятый ответ только небольшой edit)
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 файла, открытый для стороннего приложения. (Не так, как мы использовали его раньше). Поэтому измените только те места, где вы разделяете путь с приложением 3rd party (камера в моем случае)
в нашем приложении мы были отправка uri в приложение камеры, в этом месте мы ожидаем, что приложение камеры будет хранить захваченное изображение.
- для android N мы генерируем новый контент: / / URL на основе uri, указывающий на файл.
- мы генерируем обычный путь к файлу на основе api для того же (используя более старый метод).
теперь у нас есть 2 разных URI для одного файла. #1 совместно с приложением камеры. Если намерение камеры-успех, мы можем получить доступ к изображению из #2.
надеюсь, что это помогает.
Xamarin.Android
Примечание: путь xml / provider_paths.в XML (.axml) не удалось решить, даже после создания xml под ресурсы (возможно, его можно поместить в существующее место, например значения, не пробовал), поэтому я прибегнул к этому, который работает сейчас. Тестирование показало, что его нужно вызывать только один раз за запуск приложения (что имеет смысл в том, что он изменяет рабочее состояние host VM).
Примечание: xml должен быть капитализирован, так что ресурсы / 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);