android.os.FileUriExposedException: файл: ///storage/emulated/0/test.txt, доступный за пределами приложения через Intent.getData ()


739

Приложение падает, когда я пытаюсь открыть файл. Работает ниже 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-нугу file://URI больше не разрешены. Мы должны использовать content://URI вместо этого. Тем не менее, мое приложение должно открывать файлы в корневых каталогах. Любые идеи?


20
Я чувствую, что это была ошибка, которая делает жизнь разработчиков ненужной. Необходимость связать «FileProvider» и «полномочия» с каждым приложением, похоже на шаблон Enterprisey. Необходимость добавлять флаг к каждому намерению файла кажется неудобным и, возможно, ненужным. Нарушение элегантного понятия «дорожки» неприятно. И в чем выгода? Выборочное предоставление доступа к хранилищу приложениям (хотя большинство приложений имеют полный доступ к SDCard, особенно те, которые работают с файлами)?
nyanpasu64

2
попробуйте этот небольшой и совершенный код stackoverflow.com/a/52695444/4997704
Бинеш Кумар

Ответы:


1318

Если ваш targetSdkVersion >= 24, то мы должны использовать FileProviderкласс, чтобы предоставить доступ к определенному файлу или папке, чтобы сделать их доступными для других приложений. Мы создаем наш собственный класс наследования FileProvider, чтобы убедиться, что наш FileProvider не конфликтует с FileProvider, объявленными в импортированных зависимостях, как описано здесь .

Шаги по замене file://URI на content://URI:

  • Добавьте расширение класса 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}.provider"
            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);

Пожалуйста, обратитесь, полный код и решение было объяснено здесь.


62
Мне просто нужно было добавить intent.setFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
Alorma

24
Будет ли это работать для всех версий Android или только из API 24?
разработчик Android

9
эта статья помогла мне medium.com/@ali.muzaffar/...
AbdulMomen عبدالمؤمن

11
@rockhammer Я только что проверил это с Android 5.0, 6.0, 7.1 и 8.1, это работает во всех случаях. Так что (Build.VERSION.SDK_INT > M)состояние бесполезно.
Себастьян

66
FileProviderследует расширять, только если вы хотите переопределить любое поведение по умолчанию, в противном случае используйте android:name="android.support.v4.content.FileProvider". См. Developer.android.com/reference/android/support/v4/content/…
JP Ventura,

313

Помимо решения с использованием 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. Они просто вылетали, когда я пытался отправить им FileProvider uri. Сейчас это исправлено в большинстве приложений, поэтому мы должны использовать решение FileProvider.


1
@LaurynasG В API от 18 до 23 по умолчанию Android не проверяет уязвимость файла uri. Вызов этого метода включает эту проверку. Начиная с API 24, android делает эту проверку по умолчанию. Но мы можем отключить его, установив новый VmPolicy.
hqzxzwb

Есть ли другой шаг, необходимый для этого, чтобы работать? Не работает, так как он стоит на моем Moto G под управлением Android 7.0.
CKP78

3
Как это может решить эту проблему, однако, StrictMode это диагностические инструменты, которые должны быть включены в режиме разработчика, а не в режиме выпуска ???
Имена Нумене

1
@ImeneNoomene На самом деле мы отключаем StrictMode здесь. Кажется разумным, что StrictMode не должен быть включен в режиме выпуска, но на самом деле Android включает некоторые параметры StrictMode по умолчанию независимо от режима отладки или режима выпуска. Но так или иначе, этот ответ должен был стать обходным решением, когда некоторые целевые приложения не были подготовлены к получению контента. Теперь, когда в большинство приложений добавлена ​​поддержка контента Uris, мы должны использовать шаблон FileProvider.
hqzxzwb

3
@ImeneNoomene Я полностью с тобой в твоем негодовании. Вы правы, это диагностический инструмент, или, по крайней мере, это было давным-давно, когда я добавил его в свои проекты. Это супер расстраивает! StrictMode.enableDefaults();, который я запускаю только в своих сборках разработки, предотвращает этот сбой - так что теперь у меня есть производственное приложение, которое вылетает, но не вылетает в процессе разработки. Таким образом, включение диагностического инструмента здесь скрывает серьезную проблему. Спасибо, @hqzxzwb за помощь в разъяснении этого.
Джон

174

Если значение 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);

и ты в порядке. Надеюсь, поможет.


2
@MakimKniazev Можете ли вы кратко описать свою ошибку? Так что я могу вам помочь.
Панкадж

1
@PankajLilan, я сделал именно то, что ты сказал. Но каждый раз, когда я открываю свой PDF-файл в другом приложении, он отображается пустым (его сохранение правильно). Нужно ли мне редактировать XML? Я уже добавил FLAG_GRANT_READ_URI_PERMISSION;
Фелипе Кастильос

1
Моя ошибка, я добавил разрешение к неправильному умыслу. Это самый лучший и самый простой правильный ответ. Спасибо!
Фелипе Кастильос

2
Выдает исключение java.lang.IllegalArgumentException: не удалось найти настроенный корень, который содержит / Мой путь к файлу /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf. не могли бы вы помочь?
Джагдиш Бхавсар

1
Не знаю почему, но мне пришлось добавить разрешения READ и WRITE: target.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION) target.addFlags (Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
блок

160

Если ваше приложение предназначено для API 24+, и вы все еще хотите / должны использовать file: // намерения, вы можете использовать хакерский способ отключить проверку во время выполнения:

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-файла со схемой content: // предлагает гораздо меньше приложений, чем при открытии того же поверх схемы file: //. Я не хочу платить за ошибки в дизайне Google, ограничивая функциональность моего приложения.

Google хочет, чтобы разработчики использовали схему контента, но система не готова к этому, поскольку приложения создавались годами для использования файлов, а не «контента», файлы можно редактировать и сохранять обратно, а файлы, обслуживаемые по схеме контента, не могут (могут Они?).


3
msgstr "в то время как файлы, обслуживаемые по схеме содержимого, не могут быть (не так ли?)" - конечно, если у вас есть доступ для записи к контенту. ContentResolverимеет оба openInputStream()и openOutputStream(). Менее хакерский способ сделать это - просто настроить правила виртуальной машины самостоятельно и не включать file Uriправило.
CommonsWare

1
Точно. Это сложная работа, когда вы создали все свое приложение, а затем выясните, после того как нацелены на все 25 методов вашей камеры. Это работает для меня, пока у меня не будет времени, чтобы сделать это правильно.
Мэтт W

5
Работает на Android 7. Спасибо
Антон Кизема

4
Работает и на Android 8, протестировано на Huawei Nexus 6P.
Гонсало Ледезма Торрес

4
Я подтверждаю, что это работает на производстве (у меня более 500 000 пользователей), в настоящее время версия 8.1 является самой высокой версией, и она работает на нем.
Эли

90

Если у вас targetSdkVersion24 или выше, вы не можете использовать 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);

(из этого примера проекта )


Спасибо за ответ. Что происходит, когда я использую это с файлами в /systemразделе? Каждое приложение должно иметь доступ к этому разделу без рута.
Томас Вос

2
@SuperThomasLab: я бы не стал рассчитывать на то, что /systemстанет читабельным. При этом, я предполагаю, что вы все равно получите это исключение. Я подозреваю, что они просто проверяют схему и не пытаются определить, действительно ли файл доступен для чтения. Тем FileProviderне менее, это не поможет вам, так как вы не можете научить его служить /system. Вы можете создать собственную стратегию для меняStreamProvider или накатить свою собственную ContentProvider, чтобы обойти проблему.
CommonsWare

Все еще думаю, как я собираюсь решить эту проблему .. Приложение, которое я обновляю с поддержкой Android N, является корневым браузером. Но теперь вы не можете больше открывать файлы в корневых каталогах. ( /data, /system), из-за этого "хорошего изменения".
Томас Вос

1
Каковы наиболее важные недостатки снижения targetSdkVersion до 23? Thnx
rommex

2
@rommex: я не знаю, что можно назвать «самым важным». Например, пользователям, которые работают в режиме разделенного экрана или на многооконных устройствах произвольной формы (Chromebook, Samsung DeX), сообщат, что ваше приложение может не работать с многооконным режимом. Важно это или нет, решать только вам.
CommonsWare

47

Сначала вам нужно добавить провайдера в ваш 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. Я проверил этот код, и он работает.


1
Спасибо тебе за это. Я также хочу, чтобы вы знали, что есть лучший способ получить расширение файла. String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); Кроме того, я рекомендую всем, кто ищет ответы, сначала прочитать FileProvider и понять, с чем вы имеете дело здесь с правами доступа к файлам в Android N и выше. Существуют варианты для внутреннего хранилища и внешнего хранилища, а также для обычного пути к файлу и пути кеша.
praneetloke

2
Я получал следующее исключение: java.lang.IllegalArgumentException: Failed to find configured root ...и единственное, что сработало, было <files-path path="." name="files_root" />вместо файла XML <external-path .... Мой файл был сохранен во внутреннем хранилище.
steliosf

26

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

Это поможет мне исправить сбой файлов с внешних хранилищ. Надеюсь, это поможет кому-то, у кого возникла та же проблема, что и у меня :)


1
Где вы нашли, <root-pathпожалуйста? Работает. <external-path path="Android/data/${applicationId}/" name="files_root" />не имел никакого эффекта для открытых файлов из внешнего хранилища.
T0M

я нахожу это из различных результатов поиска, позвольте мне проверить еще раз и вернуться к вам как можно скорее
Рамз

Вы также упоминаете внешнее хранилище SD-карты или встроенное хранилище?
Рамз

Извините за неточность. Я имел ввиду Android/data/${applicationId}/в SDcard.
T0M

1
Нужно добавить это к цели: intent.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
S-Hunter

26

Мое решение было «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, doesn't work in Android 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

Кажется, что fromFile () использует указатель файла, который, я полагаю, может быть небезопасным, когда адреса памяти открыты для всех приложений. Но путь к файлу String никогда никому не причиняет вреда, поэтому он работает без исключения FileUriExposedException.

Проверено на уровнях API от 9 до 27! Успешно открывает текстовый файл для редактирования в другом приложении. Не требует ни FileProvider, ни библиотеки поддержки Android.


Я бы хотел увидеть это первым. Я не доказал, что это работает для меня, но это гораздо менее громоздко, чем FileProvider.
Дейл

Примечание о том, почему это действительно работает: проблема связана не с указателем файла, а с тем фактом, что исключение возникает только в том случае, если у вас есть путь с «file: //», которому автоматически предшествует fromFile, но не разбирается ,
Xmister

3
Это не исключение, но оно также не может отправить файл в соответствующее приложение. Так что, не работал для меня.
Сердар Саманджоглу

1
Это не удастся на Android 10 и выше, так как вы не можете предполагать, что другое приложение имеет доступ к внешнему хранилищу через файловую систему.
CommonsWare

24

Просто вставьте приведенный ниже код в активность onCreate ()

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

Это будет игнорировать воздействие URI


1
это одно из решений, но не стандартное. Тем не менее, люди, которые отклонили ответы, ошибочны, так как это также рабочий код с рабочим решением.
Сакшам

23

Просто вставьте приведенный ниже код в активность onCreate().

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); 
StrictMode.setVmPolicy(builder.build());

Он будет игнорировать воздействие URI.

Удачного кодирования :-)


1
Каковы недостатки этого?
Джеймс Ф

1
Это не удастся на Android 10 и выше, так как вы не можете предполагать, что другое приложение имеет доступ к внешнему хранилищу через файловую систему.
CommonsWare

18

Использование fileProvider - путь. Но вы можете использовать этот простой обходной путь:

ВНИМАНИЕ : это будет исправлено в следующем выпуске Android - https://issuetracker.google.com/issues/37122890#comment4

заменить:

startActivity(intent);

по

startActivity(Intent.createChooser(intent, "Your title"));

7
Вскоре Google выберет тот, кто выберет тот же чек. Это не решение.
Указатель

Этот работает, но не будет работать в будущих версиях Android.
Дилжит

13

Я использовал ответ Палаша, приведенный выше, но он был несколько неполным, я должен был дать такое разрешение

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

11

Просто вставьте приведенный ниже код в активность onCreate ()

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());

Это будет игнорировать воздействие URI


Это удалит политики строгого режима. и будет игнорировать предупреждение безопасности. Не очень хорошее решение
inspire_coding

Это также не удастся на Android 10 и выше, так как вы не можете предположить, что другое приложение имеет доступ к внешнему хранилищу через файловую систему.
CommonsWare

7

добавить эти две строки в 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..."));

Это не удастся на Android 10 и выше, так как вы не можете предполагать, что другое приложение имеет доступ к внешнему хранилищу через файловую систему.
CommonsWare

7

Вот мое решение:

в 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, и это успешно работает.


Я протестировал это решение, и оно отлично работает с небольшим изменением: intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra (Intent.EXTRA_STREAM, myPhotoFileUri) intent.type = "image / png" startActivity (Intent.createChooser (intent, ") Поделиться изображением через ")) Это работает плавник на Android 7 и 8.
inspire_coding

4

Для загрузки 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>

1
что такое @xml/provider_paths?
adityasnl

1
@Heisenberg, пожалуйста, отошлите сообщение Рахула Упадхая от URL: stackoverflow.com/questions/38555301/…
Bhoomika Chauhan

3

Я не знаю почему, я сделал все точно так же, как 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.


2

Я поставил этот метод, чтобы путь 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);
}

2
As of Android N, in order to work around this issue, you need to use the FileProvider API

Здесь есть 3 основных шага, как указано ниже

Шаг 1: Манифестная запись

<manifest ...>
    <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>
</manifest>

Шаг 2. Создайте файл 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>

Шаг 3: Изменения кода

File file = ...;
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Old Approach
    install.setDataAndType(Uri.fromFile(file), mimeType);
// End Old approach
// New Approach
    Uri apkURI = FileProvider.getUriForFile(
                             context, 
                             context.getApplicationContext()
                             .getPackageName() + ".provider", file);
    install.setDataAndType(apkURI, mimeType);
    install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// End New Approach
    context.startActivity(install);

1

Я знаю, что это довольно старый вопрос, но этот ответ для будущих зрителей. Итак, я столкнулся с подобной проблемой, и после исследования я нашел альтернативу этому подходу.

Ваше намерение здесь, например: для просмотра изображения с вашего пути в 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, и в моем случае он работал просто отлично.


0

Я провел почти день, пытаясь понять, почему я получаю это исключение. После долгой борьбы этот конфиг работал отлично ( Kotlin ):

AndroidManifest.xml

<provider
  android:name="androidx.core.content.FileProvider"
  android:authorities="com.lomza.moviesroom.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

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path name="movies_csv_files" path="."/>
</paths>

Сам намерение

fun goToFileIntent(context: Context, file: File): Intent {
    val intent = Intent(Intent.ACTION_VIEW)
    val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
    val mimeType = context.contentResolver.getType(contentUri)
    intent.setDataAndType(contentUri, mimeType)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    return intent
}

Я объясняю весь процесс здесь .


-1

https://stackoverflow.com/a/38858040/395097 этот ответ завершен.

Ответ на этот вопрос - у вас уже есть приложение с таргетингом ниже 24, и теперь вы переходите на targetSDKVersion> = 24.

В Android N изменяется только файл, доступный стороннему приложению. (Не так, как раньше). Поэтому меняйте только те места, где вы делите путь, с помощью стороннего приложения (камера в моем случае)

В нашем приложении мы отправляли Uri в приложение Camera, в этом месте мы ожидаем, что приложение Camera сохранит захваченное изображение.

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

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

Надеюсь это поможет.


1
Вы ссылаетесь на уже опубликованный здесь ответ, если вам необходимо его заполнить, прокомментируйте ответ, плз.
IgniteCoders

1
@IgniteCoders Как я ясно упомянул в сообщении, мой ответ охватывает связанный вариант использования.
Арам

-1

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

-1

Ответ @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.


-1

Просто позвольте ему игнорировать URI Exposure ... Добавьте его после создания

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); 

Это не удастся на Android 10 и выше, так как вы не можете предполагать, что другое приложение имеет доступ к внешнему хранилищу через файловую систему.
CommonsWare

Это не должно использоваться в производственном приложении.
Jorgesys

-1

Попробуйте это решение

ОСТАВЬТЕ ЭТИ РАЗРЕШЕНИЯ В МАНИФЕСТ

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.CAMERA" />

Намерение захватить изображение

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                }

ПОЛУЧИТЬ ИЗОБРАЖЕНИЕ В ONACTIVITYRESULT

@Override
            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                super.onActivityResult(requestCode, resultCode, data);
                if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
                    Bundle extras = data.getExtras();
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    // CALL THIS METHOD TO GET THE URI FROM THE BITMAP
                    Uri tempUri = getImageUri(getApplicationContext(), imageBitmap);
                    //DO SOMETHING WITH URI
                }
            } 

СПОСОБ ПОЛУЧЕНИЯ URI ИЗОБРАЖЕНИЯ

public Uri getImageUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }

Может кто-нибудь сказать мне, почему голосование против. это 100% рабочий раствор.
Абдул Басит Риши

Это только дает вам эскиз, а не полную картину.
Build3r

-2

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.