андроид.ОС.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+.

ваш выбор:

  1. брось targetSdkVersion в 23 или ниже, или

  2. поместите свой контент во внутреннее хранилище, затем использовать 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 в приложение камеры, в этом месте мы ожидаем, что приложение камеры будет хранить захваченное изображение.

  1. для android N мы генерируем новый контент: / / URL на основе uri, указывающий на файл.
  2. мы генерируем обычный путь к файлу на основе api для того же (используя более старый метод).

теперь у нас есть 2 разных URI для одного файла. #1 совместно с приложением камеры. Если намерение камеры-успех, мы можем получить доступ к изображению из #2.

надеюсь, что это помогает.


в моем случае я избавился от исключения, заменив SetDataAndType с SetData.


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);