Android.os.FileUriExposedException: файл:///storage/emulated/0/test.txt выставляется вне приложения через Intent.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
Журнал:
android.os.FileUriExposedException: файл:///storage/emulated/0/test.txt выставляется вне приложения через Intent.getData()
Редактировать:
При настройке на Android Nougat file://
URI больше не разрешены. Мы должны использовать content://
URI. Однако моему приложению нужно открывать файлы в корневых каталогах. Есть идеи?
Ответы
Ответ 1
Если ваш targetSdkVersion >= 24
, то мы должны использовать класс FileProvider
, чтобы предоставить доступ к определенному файлу или папке, чтобы сделать их доступными для других приложений. Мы создаем наш собственный класс, наследующий FileProvider
, чтобы убедиться, что наш FileProvider не конфликтует с FileProvider, объявленными в импортированных зависимостях, как описано здесь.
Действия по замене URI file://
на URI content://
:
Добавьте расширение класса FileProvider
public class GenericFileProvider extends FileProvider {}
Добавьте тег FileProvider <provider>
в AndroidManifest.xml
под тегом <application>
. Укажите уникальные права доступа для атрибута 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}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
- Затем создайте файл
provider_paths.xml
в папке res/xml
. Папка может понадобиться для создания, если она не существует. Содержание файла показано ниже. В нем описывается, что мы хотели бы поделиться доступом к внешнему хранилищу в корневой папке (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() + ".provider", createImageFile());
Изменить: Если вы намерены заставить систему открыть файл, вам может потребоваться добавить следующую строку кода:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Пожалуйста, обратитесь, полный код и решение было объяснено здесь.
Ответ 2
Помимо решения, использующего FileProvider
, есть еще один способ обойти это. Проще говоря
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
в Application.onCreate()
. Таким образом, VM игнорирует экспозицию URI
файла.
метод
builder.detectFileUriExposure()
позволяет проверить экспозицию файла, что также является поведением по умолчанию, если мы не настроим VmPolicy.
Я столкнулся с проблемой, что, если я использую URI
content://
чтобы отправить что-то, некоторые приложения просто не могут этого понять. И понижение target SDK
версии target SDK
не допускается. В этом случае мое решение полезно.
Обновить:
Как упоминалось в комментарии, StrictMode является диагностическим инструментом и не должен использоваться для этой проблемы. Когда я опубликовал этот ответ год назад, многие приложения могут получать только файл uris. Они просто сбой, когда я пытался отправить url FileProvider им. В большинстве случаев это исправлено в большинстве приложений, поэтому мы должны пойти с решением FileProvider.
Ответ 3
Если ваше приложение предназначено для API 24+, и вы все еще хотите/должны использовать файл://intents, вы можете использовать хакерский способ отключить проверку времени выполнения:
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.
*/
Проблема в том, что мое приложение не является хромым, но скорее не хочет быть искалеченным с помощью контента://намерений, которые не понятны многим приложениям там. Например, открытие mp3 файла с контентом://схема предлагает гораздо меньше приложений, чем при открытии такого же файла://. Я не хочу платить за ошибки дизайна Google, ограничивая возможности моего приложения.
Google хочет, чтобы разработчики использовали схему контента, но система не готова к этому, в течение многих лет приложения были сделаны для использования файлов, а не "содержимого", файлы могут быть отредактированы и сохранены обратно, в то время как файлы, обслуживаемые по схеме контента, не могут (может они?).
Ответ 4
Если 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>
Если вы используете androidx, путь FileProvider должен быть:
android:name="androidx.core.content.FileProvider"
и заменить
Uri uri = Uri.fromFile(fileImagePath);
в
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
Изменить: пока вы включаете URI с Intent
убедитесь, что добавили строку ниже:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
и ты в порядке. Надеюсь, поможет.
Ответ 5
Если ваш 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);
(из этот пример проекта)
Ответ 6
Сначала вам нужно добавить поставщика на 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-студию, вы можете нажать Alt + Enter после выделения файлов_пакетов и выбрать создать параметр ресурса 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-карты в файлы_пакетов. Я тестировал этот код, и он работает.
Ответ 7
@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)
Это поможет мне исправить ошибку для файлов из внешних хранилищ. Надеюсь, это поможет кому-то с той же проблемой, что и моя
:)
Ответ 8
Просто вставьте приведенный ниже код в действие onCreate()
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
Он игнорирует воздействие URI
Ответ 9
Мое решение было "Uri.parse" путь к файлу в виде строки, а не с помощью Uri.fromFile().
String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
uri = Uri.fromFile(file);
} else {
uri = Uri.parse(file.getPath()); // My work-around for new SDKs, causes ActivityNotFoundException in API 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);
Кажется, что fromFile() использует указатель файла, который, я полагаю, может быть небезопасным, когда адреса памяти открыты для всех приложений. Но путь к файлу String никогда никому не причиняет вреда, поэтому он работает без исключения FileUriExposedException.
Проверено на уровнях API от 9 до 27! Успешно открывает текстовый файл для редактирования в другом приложении. Не требует ни FileProvider, ни библиотеки поддержки Android.
Ответ 10
Использование fileProvider - путь.
Но вы можете использовать это обходное решение:
ПРЕДУПРЕЖДЕНИЕ. Он будет исправлен в следующем выпуске Android - https://issuetracker.google.com/issues/37122890#comment4
заменить:
startActivity(intent);
по
startActivity(Intent.createChooser(intent, "Your title"));
Ответ 11
Просто вставьте приведенный ниже код в активность onCreate()
.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Он будет игнорировать воздействие URI.
Удачного кодирования :-)
Ответ 12
Я использовал приведенный выше ответ "Палаш", но он был несколько неполным, я должен был предоставить такое разрешение
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);
Ответ 13
Просто вставьте приведенный ниже код в активность onCreate()
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
Это будет игнорировать воздействие URI
Ответ 14
добавить эти две строки в onCreate
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Поделиться метод
File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));
Ответ 15
Для загрузки 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>
Ответ 16
Вот мое решение:
в Manifest.xml
<application
android:name=".main.MainApp"
android:allowBackup="true"
android:icon="@drawable/ic_app"
android:label="@string/application_name"
android:logo="@drawable/ic_app_logo"
android:theme="@style/MainAppBaseTheme">
<provider
android:name="androidx.core.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>
в 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>
в моем фрагменте у меня есть следующий код:
Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);
Это все, что вам нужно.
Также не нужно создавать
public class GenericFileProvider extends FileProvider {}
Я тестирую на Android 5.0, 6.0 и Android 9.0 и это успешно работает.
Ответ 17
Я не знаю, почему, я сделал все точно так же, как Pkosta (fooobar.com/questions/22791/...), но продолжал получать ошибку:
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
самом деле intent
было установить getIntent().addFlags
вместо того, чтобы работать с моим новым объявленным playIntent.
Ответ 18
Я поставил этот метод, чтобы путь imageuri легко попасть в контент.
enter code here
public Uri getImageUri(Context context, Bitmap inImage)
{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes);
String path = MediaStore.Images.Media.insertImage(context.getContentResolver(),
inImage, "Title", null);
return Uri.parse(path);
}
Ответ 19
Я знаю, что это довольно старый вопрос, но этот ответ для будущих зрителей. Итак, я столкнулся с подобной проблемой, и после исследования я нашел альтернативу этому подходу.
Ваше намерение здесь, например: для просмотра изображения с вашего пути в Kotlin
val intent = Intent()
intent.setAction(Intent.ACTION_VIEW)
val file = File(currentUri)
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val contentURI = getContentUri(context!!, file.absolutePath)
intent.setDataAndType(contentURI,"image/*")
startActivity(intent)
Основная функция ниже
private fun getContentUri(context:Context, absPath:String):Uri? {
val cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf<String>(MediaStore.Images.Media._ID),
MediaStore.Images.Media.DATA + "=? ",
arrayOf<String>(absPath), null)
if (cursor != null && cursor.moveToFirst())
{
val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
}
else if (!absPath.isEmpty())
{
val values = ContentValues()
values.put(MediaStore.Images.Media.DATA, absPath)
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
else
{
return null
}
}
Аналогично, вместо изображения вы можете использовать любой другой формат файла, например, pdf, и в моем случае он работал просто отлично.
Ответ 20
fooobar.com/questions/22791/... этот ответ завершен.
Этот ответ для вас - у вас уже есть приложение, которое было ориентировано ниже 24, и теперь вы обновляетесь до targetSDKVersion> = 24.
В Android N изменяется только файл uri, доступный для стороннего приложения. (Не так, как мы использовали его раньше). Поэтому измените только те места, где вы делитесь своим путем с сторонним приложением (камера в моем случае)
В нашем приложении мы отправляли uri в приложение Camera, в этом месте мы ожидаем, что приложение камеры сохранит захваченное изображение.
- Для android N мы создаем новый URL-адрес Content://uri, указывающий на файл.
- Мы генерируем обычный путь на основе файла api для этого же (используя более старый метод).
Теперь у нас есть 2 разных uri для одного файла. # 1 используется совместно с приложением Camera. Если намерение камеры имеет успех, мы можем получить доступ к изображению из # 2.
Надеюсь это поможет.
Ответ 21
Xamarin.Android
Примечание. Путь xml/provider_paths.xml(.axml) не может быть разрешен даже после создания папки xml в разделе "Ресурсы" (возможно, ее можно поместить в существующее расположение, например "Значения", не пытаясь), поэтому я прибег к это работает на данный момент. Тестирование показало, что его нужно вызывать только один раз за запуск приложения (что имеет смысл в том, что оно изменяет рабочее состояние виртуальной машины хоста).
Примечание: 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);
Ответ 22
Ответ @Pkosta - один из способов сделать это.
Помимо использования FileProvider
, вы также можете вставить файл в MediaStore
(особенно для файлов изображений и видео), поскольку файлы в MediaStore доступны для каждого приложения:
MediaStore в первую очередь нацелен на MIME-типы видео, аудио и изображений, однако, начиная с Android 3.0 (уровень API 11), он также может хранить не мультимедийные типы (для получения дополнительной информации см. MediaStore.Files). Файлы могут быть вставлены в MediaStore с помощью scanFile(), после чего URI content://, пригодный для обмена, передается в предоставленный обратный вызов onScanCompleted(). Обратите внимание, что после добавления в систему MediaStore содержимое доступно любому приложению на устройстве.
Например, вы можете вставить видеофайл в MediaStore следующим образом:
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
contentUri
похож на content://media/external/video/media/183473
, который может быть передан непосредственно Intent.putExtra
:
intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);
Это работает для меня, и избавить от FileProvider
с использованием FileProvider
.
Ответ 23
В моем случае я избавился от исключения, заменив SetDataAndType
на просто SetData
.