Intent.ACTION_SEND таргетинг API уровня 24+

Я получаю исключение android.os.StrictMode.onFileUriExposed при попытке отправить файл с намерением. Насколько я понимаю, это связано с тем, что я нацелен на 24>, а file:// больше не поддерживается, следует использовать content://.

Сначала я хотел бы сказать, что я видел похожие вопросы, такие как это, это, и я также видел сообщение в блоге это.

Но проблема в том, что все сообщения относятся к URI при съемке изображения, в моем случае файл успешно сохраняется с использованием Uri, и теперь я хочу отправить изображение с помощью Intent, как показано ниже:

shareBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
            Uri screenshotUri = Uri.parse("file://" + directoryToStore + "/" + filename);
            //the Uri above  - file:///storage/emulated/0/Android/data/myPackageName/files/SavedImages/test.jpeg
            sharingIntent.setType("image/jpeg");
            sharingIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
            startActivity(Intent.createChooser(sharingIntent, "Share image using"));
        }
    });

Выполняя вышеизложенное, я получаю сбой только на некоторых устройствах, работающих под управлением 19>. При тестировании на моем Samsung J7Pro (Android 7.0 API 24) сбой не возникает.

Я видел, что в некоторых из этих ответов говорится, что я могу использовать:

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

Но это не лучший способ.

Итак, мой вопрос. Как мне обрабатывать отправку файлов при таргетинге на 24>. Должен ли я выполнять оператор if / else, проверяя версию, например if (Build.VERSION.SDK_INT >= 19) {, а затем использовать обычный Uri для устройств, работающих под управлением <24, и как мне изменить file:// на content://? Я также не понимаю, почему сбой происходит только на некоторых устройствах.


РЕДАКТИРОВАТЬ 1:

Я сделал то, что предложил ответ ниже, но файл не передается с намерением, вместо этого я получаю Toast, говорящий Unable to attach file, когда я пытаюсь отправить изображение по электронной почте.

Можете ли вы проверить свой logcat и увидеть, какие ошибки / stacktrace вы получаете?

Arseny Levin 22.07.2018 13:27

@ArsenyLevin Я не получаю никаких ошибок, когда я нажимаю кнопку «Поделиться», я получаю только ViewPostImeInputStage processPointer 1, затем я получаю средство выбора, в котором я могу выбрать приложение для обмена изображением.

HB. 22.07.2018 13:32

@ArsenyLevin Я зарегистрировал Uri contentUri = FileProvider.getUriForFile(getApplication(),getApplication()‌​.getPackageName() + ". MyFileProvider", newFile);, и он возвращает shared-files вместо SavedImages

HB. 22.07.2018 13:35

измените xml на <files-path name = "SavedImages" path = "SavedImages/"/>

Arseny Levin 22.07.2018 13:42

@ArsenyLevin Я по-прежнему получаю тот же тост, что и моя редакция выше. Uri теперь возвращает content://my.packahe.name.MyFileProvider/SavedImages/test.jp‌​eg

HB. 22.07.2018 13:59

проверьте мою правку ниже. на основе документов здесь: developer.android.com/reference/android/support/v4/content/…

Arseny Levin 22.07.2018 14:15

@ArsenyLevin Спасибо, что нашли время ответить, но теперь я получаю эту ошибку -Failed to find configured root that contains /data/data/my.package.name/files/SavedImages/test.jpeg

HB. 22.07.2018 14:22

извините за долгий пинг-понг. попробуйте изменить элемент xml на <external-path, а НЕ на <external-files-path, как я предлагал ранее.

Arseny Levin 22.07.2018 14:28

Позвольте нам продолжить обсуждение в чате.

HB. 22.07.2018 14:32
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
9
991
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Правильный способ обмена файлами из вашего приложения - это content provider, в частности FileProvider.

Добавьте свой FileProvider в AnroidManifest.xml:

<provider
    android:name = "android.support.v4.content.FileProvider"
    android:authorities = "${applicationId}.MyFileProvider"
    android:exported = "false"
    android:grantUriPermissions = "true" >
    <meta-data
        android:name = "android.support.FILE_PROVIDER_PATHS"
        android:resource = "@xml/shared_paths"/>
</provider>

Затем добавьте ресурс, на который имеется ссылка, путем добавления ресурса xml (Android Studio: щелкните правой кнопкой мыши «приложение» -> «Создать» -> «Папка» -> «Сборщик ресурсов XML»). Затем внутри этой новой папки создайте XML-файл с именем shared_paths.xml (должен соответствовать значению в AndroidManifest.xml). Содержимое для shared_paths.xml:

<?xml version = "1.0" encoding = "utf-8"?>
<paths xmlns:android = "http://schemas.android.com/apk/res/android">
    <external-path name = "SavedImages" path = "SavedImages/"/>
</paths>

Обновлено: изменен тип элемента с <files-path ../> на <external-path .. /> на документация. ВАЖНО: Измените атрибут path в соответствии со своими потребностями.

Наконец, измените код следующим образом:

shareBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
            Uri screenshotUri = FileProvider.getUriForFile(context, context.getPackageName() + ".MyFileProvider", new File(directoryToStore, filename))
            //the Uri above  - file:///storage/emulated/0/Android/data/myPackageName/files/SavedImages/test.jpeg
            sharingIntent.setType("image/jpeg");
            sharingIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
            sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Intent chooserIntent = Intent.createChooser(sharingIntent, "Share image using");
            chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(chooserIntent);
        }
    });

Если вы сохраняете свой файл в общедоступной папке мультимедиа во внутренней памяти (например, для изображений /storage/emulated/0/DCIM/my.package.name/SavedImages/myImage.jpg), вы также можете поделиться файлом через его content- media uri (для содержимого изображения: // media / external / images / media / 4711).

Таким образом, вам не понадобится собственный FileProvider, но файл будет доступен всем.

С помощью lcoal FileProvider в каталогах личных данных приложений файл доступен только через ваше приложение и будет удален, если вы удалите приложение.

цель Contentprovider состоит в том, чтобы вы могли читать контент из любого uri как inputStream (т.е. форма «uri =« https://stackoverflow.com/questions/ ... »,« file: /// storage / emulated / 0 / ... »,« ftp :. ... "," content: ... "), если для этого есть поставщик.

с api 25 Google требует, чтобы uri-файлы "file: ..." больше не допускались

Спасибо за ваш ответ, я хочу, чтобы файлы оставались конфиденциальными, и я хочу, чтобы они были удалены после удаления приложения. Я хочу, чтобы мое приложение могло получить доступ к файлам, если только я не хочу делиться файлами из приложения y.

HB. 23.07.2018 13:36

для личных файлов вам понадобится поставщик контента или поставщик документов

k3b 23.07.2018 13:42

Спасибо. Мой минимальный SDK - 16, должен ли я проверить, является ли SDK 19 <, а затем выполнить его старым способом, используя file://?

HB. 23.07.2018 13:45

это зависит от ваших клиентских приложений, получающих изображение. некоторые могут обрабатывать file: // некоторые могут обрабатывать content: //, а некоторые могут обрабатывать и то, и другое. используйте github.com/k3b/intent-intercept и github.com/k3b/ContentProviderHelper для анализа намерений и контент-провайдера. мой imageapp может обрабатывать оба и скоро получит настройку конфигурации, чтобы открыть file: // или content: //, где по умолчанию будет content: //

k3b 23.07.2018 13:52

и это только усложняется .. Спасибо, что нашли время ответить на мой вопрос.

HB. 23.07.2018 14:00

Вчера вечером я начал думать о вашем последнем комментарии. К концу месяца все приложения должны быть ориентированы на 24+, так что не означает ли это, что с конца месяца большинство приложений смогут обрабатывать content://? В этом был смысл принуждения разработчиков приложений ориентироваться на 24+ в первую очередь из соображений безопасности?

HB. 24.07.2018 07:42

если все отправители-получатели запрограммированы вами, то да. если вы используете приложения, которые последний раз изменялись 4 года назад, это означает, что нет. в случае сомнений следует использовать «content:». после переосмысления этой темы: мое приложение в настоящее время использует отправку / просмотр через «файл», и я внесу несовместимое изменение в «content:» без страницы конфигурации. это означает меньше программирования, меньше элементов графического интерфейса меньше пользовательской документации.

k3b 24.07.2018 11:31

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