android.os.FileUriExposedException: файл: ///storage/emulated/0/test.txt, відкритий поза додатком через Intent.getData ()


738

Додаток виходить з ладу, коли я намагаюся відкрити файл. Він працює нижче 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. Однак моєму додатку потрібно відкривати файли в кореневих каталогах. Будь-які ідеї?


20
Я вважаю, що це була помилка, яка ускладнює життя розробникам додатків. Потрібно поєднати "FileProvider" та "повноваження" з кожним додатком, схоже, на коробці Enterprisey. Додавання прапора до кожного наміру файлу здається незручним і, можливо, непотрібним. Порушити елегантну концепцію "доріжки" неприємно. А яка користь? Вибіркове надання доступу до пам’яті до додатків (хоча більшість програм має повний доступ до SDCard, особливо тих, що працюють над файлами)?
nyanpasu64

2
спробувати це, маленький і досконалий код stackoverflow.com/a/52695444/4997704
Binesh Kumar

Відповіді:


1316

Якщо ваш targetSdkVersion >= 24, то ми повинні використовувати FileProviderклас, щоб надати доступ до певного файлу чи папки, щоб зробити їх доступними для інших додатків. Ми створюємо власний клас успадкування FileProviderдля того, щоб переконатися, що наш FileProvider не конфліктує з FileProviders, оголошеними в імпортних залежностях, як описано тут .

Кроки для заміни file://URI на content://URI:

  • Додати клас розширення FileProvider

    public class GenericFileProvider extends FileProvider {}
  • Додайте <provider>тег FileProvider 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);
алорма

24
Чи буде це працювати для всіх версій Android або просто від API 24?
андроїд розробник

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(). Таким чином, VM ігнорує URIекспозицію файлу .

Метод

builder.detectFileUriExposure()

дозволяє перевірити експозицію файлів, що також є поведінкою за замовчуванням, якщо ми не встановимо VmPolicy.

У мене виникла проблема, що якщо я використовую щось content:// URIдля надсилання, деякі програми просто не можуть зрозуміти це. І пониження target SDKверсії заборонено. У цьому випадку корисне моє рішення.

Оновлення:

Як зазначалося в коментарі, StrictMode - це діагностичний інструмент, і його не слід використовувати для цієї проблеми. Коли я опублікував цю відповідь рік тому, багато програм можуть отримувати лише файли uris. Вони просто розбиваються, коли я намагався надіслати їм урі FileProvider. Зараз це виправлено в більшості додатків, тому нам слід вирішити 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 за допомогу мені демістифікувати це.
Jon

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
@MaksimKniazev Чи можете ви коротко описати свою помилку? Так що я можу вам допомогти.
Панкай Лілан

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+ і ви все ще хочете / потрібно використовувати файл: // наміри, ви можете скористуватися способом відключення перевірки часу виконання:

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

Google хоче, щоб розробники використовували контент-схему, але система до цього не готується, роками прикладалися програми, щоб використовувати Файли не "контент", файли можна редагувати та зберігати назад, тоді як файли, подані за схемою вмісту, не можуть бути (можна Вони?).


3
"в той час як файли, подані за схемою вмісту, не можуть бути (можна?)." - обов'язково, якщо у вас є доступ до вмісту для запису. ContentResolverмає і те, openInputStream()і openOutputStream(). Менш хакізним способом цього є просто налаштувати правила VM самостійно , а не ввімкнути 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 (якщо ви користуєтеся андроїд-студією, ви можете натиснути 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. Я перевірив цей код, і він працює.


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

я знаходжу це з різних результатів пошуку, дозвольте перевірити ще раз і повернусь до u asap
Ramz

також зовнішнє сховище, яке ви згадуєте, - це SD-карта чи вбудований накопичувач?
Рамз

Вибачте за неточність. Я мав Android/data/${applicationId}/на увазі в SDcard.
t0m

1
Потрібно додати це до наміру: intent.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
s-мисливець

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 () використовує вказівник на файл, який, напевно, може бути незахищеним, коли адреси пам'яті піддаються всім програмам. Але Строковий шлях до файлу ніколи нікому не шкодить, тому він працює без викидання FileUriExposedException.

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


Я б хотів, щоб я це бачив першим. Я не довів, що це працює на мене, але він настільки менш громіздкий, ніж FileProvider.
Дейл

Примітка про те, чому це насправді працює: Викликає проблему не вказівник на файл, а факт, що виняток виникає лише у тому випадку, якщо у вас є шлях з файлом 'file: //', який автоматично є попереднім у файлі fromFile, але не з розбором .
Кмістер

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/isissue/37122890#comment4

замінити:

startActivity(intent);

від

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

7
Вибір скоро буде виправлений Google, щоб містити такий самий чек. Це не рішення.
Pointer Null

Цей працює, але не працюватиме у майбутніх версіях 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 = новий 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 (наміри, " Поділитися зображенням через ")) Це працює добре на 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 см Рахула Upadhyay поста з 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

Я ставлю цей метод, щоб шлях із зображеннями легко потрапляв у зміст.

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

Я знаю, що це досить старе питання, але ця відповідь призначена для майбутніх глядачів. Тому я зіткнувся з подібною проблемою, і після дослідження я знайшов альтернативу такому підходу.

Тут ви маєте намір, наприклад: Щоб переглянути своє зображення зі свого шляху в Котліні

 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

Я витратив майже день, намагаючись зрозуміти, чому я отримую цей виняток. Після великої боротьби цей конфігуратор справно працював ( Котлін ):

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, і в цьому місці ми очікуємо, що додаток камери може зберігати відзняте зображення.

  1. Для android N ми створюємо новий Content: // urri на основі URL-адреси, що вказує на файл.
  2. Ми генеруємо звичайний шлях на основі файлу api для того самого (використовуючи старіший метод).

Тепер у нас є два різних uri для одного файлу. №1 надано додатку Camera. Якщо намір камери вдалий, ми можемо отримати доступ до зображення з №2.

Сподіваюсь, це допомагає.


1
Ви посилаєтесь на вже опубліковану тут відповідь, якщо вам потрібно її заповнити, прокоментуйте у відповіді plz.
IgniteCoders

1
@IgniteCoders Як я чітко згадував у повідомленні, моя відповідь стосується відповідного випадку використання.
Арам

-1

Xamarin.Android

Примітка: шлях xml / provider_paths.xml (.axml) не вдалося вирішити, навіть після створення папки xml в розділі Resources (можливо, його можна помістити в існуюче місце на зразок Значення , не намагався), тому я вдався до це працює зараз. Тестування показало, що його потрібно викликати лише один раз запуск програми (що має сенс у тому, що це змінює операційний стан 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);

-1

Відповідь @Pkosta - це один із способів цього зробити.

Окрім використання FileProvider, ви також можете вставити файл MediaStore(особливо для файлів із зображеннями та відео), оскільки файли в MediaStore доступні для кожного додатка:

MediaStore в основному орієнтований на типи MIME для відео, аудіо та зображень, проте, починаючи з Android 3.0 (рівень API 11), він також може зберігати немедіа типи (для отримання додаткової інформації див. MediaStore.Files). Файли можна вставити в MediaStore за допомогою scanFile (), після чого вміст: // стилю Uri, придатного для спільного доступу, передається наданому зворотному виклику 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 ... Додайте його після створення

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

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

Це не потрібно використовувати у програмі для виробництва.
Хорхесіс

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

Отримати зафіксований знімок в АНАКТИВНОСТІ РЕЗУЛЬТАТУ

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

МЕТОД ДО отримання УРІ ЗОБРАЖЕННЯ

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.