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 вместо этого. Тем не менее, мое приложение должно открывать файлы в корневых каталогах. Есть идеи?

31 ответ

Решение

Если твой 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}.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>
  • Затем создайте 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() + ".my.package.name.provider", createImageFile());
    
  • Изменить: Если вы используете намерение, чтобы заставить систему открыть ваш файл, вам может потребоваться добавить следующую строку кода:

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

Если 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);

в

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);

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

Если ваше приложение предназначено для 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 хочет, чтобы разработчики использовали схему контента, но система не подготовлена ​​к этому, поскольку приложения создавались годами для использования файлов, а не "контента", файлы можно редактировать и сохранять обратно, тогда как файлы, обслуживаемые по схеме контента, не могут (могут O ни?).

Если твой 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);

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

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

Мое решение было "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 до 26! Не требует ни FileProvider, ни библиотеки поддержки Android.

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

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

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

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

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

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

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

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

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

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

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

заменить:

startActivity(intent);

от

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

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

StrictMode.VmPolicy.Builder builder = новый StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

Он будет игнорировать раскрытие URI

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

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

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

Я не знаю почему, я сделал все точно так же, как Pkosta ( /questions/2411180/androidosfileuriexposedexception-fajlstorageemulated0testtxt-dostupnyij-za-predelami-prilozheniya-cherez-intentgetdata/2411208#2411208), но продолжал получать ошибку:

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>

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

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
}

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

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

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

Я только что сделал следующее, если версия Android> 24

      File fl = new File(url);
    Uri uri = Uri.fromFile(fl);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    if (android.os.Build.VERSION.SDK_INT>=24)
    {
        Context context = getApplicationContext();
        uri = FileProvider.getUriForFile(
                context,
                context.getApplicationContext()
                        .getPackageName() + ".provider", fl);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
    intent.setDataAndType(uri, mimetype);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

По этой ссылке https://medium.com/@ali.muzaffar/what-is-android-os-fileuriexposedexception-and-what-you-can-do-about-it-70b9eb17c6d0#.54odzsnk4

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

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

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

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

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

Суть в том, что вы не можете поделиться чем-либо из хранилища с ограниченным объемом приложения. Также в Android 12 в нижнем диалоговом окне выбора намерений отображается предварительный просмотр изображения, которым вы делитесь, что, кстати, очень круто, но оно не может загрузить предварительный просмотр из URI хранилища с заданной областью.

Решение состоит в том, чтобы создать копию файла, которым вы «собираетесь» поделиться, в каталоге кеша.

      val cachePath = File(externalCacheDir, "my_images/")
cachePath.mkdirs()
val bitmap = loadImageFromStorage(currentQuote.bookId)
val file = File(cachePath, "cache.png")
val fileOutputStream: FileOutputStream
try {
    fileOutputStream = FileOutputStream(file)
    bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
    fileOutputStream.flush()
    fileOutputStream.close()
} catch (e: FileNotFoundException) {
    e.printStackTrace()
} catch (e: IOException) {
    e.printStackTrace()
}
val cacheImageUri: Uri = FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
            
val intent = Intent(Intent.ACTION_SEND).apply {
    clipData = ClipData.newRawUri(null, cacheImageUri)
    putExtra(Intent.EXTRA_STREAM, cacheImageUri)
    type = "image/ *"
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, null))
            

И вот как я загружаю файл из хранилища с ограниченным объемом

      fun Context.loadImageFromStorage(path: String): Bitmap? {
    try {
        val file = getFile(path)
        val bitmap = BitmapFactory.decodeStream(FileInputStream(file))
        return bitmap
    } catch (e: Exception) {
        e.printStackTrace()

        //Returning file from public storage in case the file is stored in public storage 
        return BitmapFactory.decodeStream(FileInputStream(File(path)))
    }
    
    return null
}


fun Context.getFile(path: String): File? {
    val cw = ContextWrapper(this)
    val directory = cw.getDir("image_dir", Context.MODE_PRIVATE)
    if (!directory.exists())
        directory.mkdir()
    try {
        val fileName = directory.absolutePath + "/" + path.split("/").last()
        return File(fileName)
    } catch (e: Exception) {
        e.printStackTrace()
    }
    
    return null
}

Наконец, не забудьте обновить provider_paths.xml файл

      <external-cache-path name="external_cache" path="." />

<external-cache-path name="external_files" path="my_images/"/>

https://developer.android.com/reference/androidx/core/content/FileProvider

Эта ссылка предоставляет все решения для всех ситуаций, обязательно прочтите раздел о предоставлении разрешений через Intent и упакуйте в соответствии с требованиями.

следует использовать ссылку ниже для api<24https://developer.android.com/reference/android/support/v4/content/FileProvider.html

чтобы узнать больше об исключении, посетите эту ссылку https://developer.android.com/reference/android/os/FileUriExposedException

/questions/2411180/androidosfileuriexposedexception-fajlstorageemulated0testtxt-dostupnyij-za-predelami-prilozheniya-cherez-intentgetdata/2411208#2411208 этот ответ завершен.

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

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

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

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

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

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

Это работает

       val uri = if (Build.VERSION.SDK_INT < 24) Uri.fromFile(file) else Uri.parse(file.path)
                val shareIntent = Intent().apply {
                    action = Intent.ACTION_SEND
                    type = "application/pdf"
                    putExtra(Intent.EXTRA_STREAM, uri)
                    putExtra(
                        Intent.EXTRA_SUBJECT,
                        "Purchase Bill..."
                    )
                    putExtra(
                        Intent.EXTRA_TEXT,
                        "Sharing Bill purchase items..."
                    )
                }
                startActivity(Intent.createChooser(shareIntent, "Share Via"))

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

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

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

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

Другие вопросы по тегам