Flutter: как разрешить файлу быть открытым внешним приложением (например, неявным намерением Android)?
Я создаю приложение Flutter, которое по существу извлекает данные из облака.
Тип данных варьируется, но обычно это изображение, pdf, текстовый файл или архив (zip-файл).
Теперь я хочу запустить неявное намерение, чтобы пользователь мог выбрать свое любимое приложение для обработки полезной нагрузки.
Я искал ответы, и я попробовал следующие маршруты:
- плагин url_launcher (как предложено в Как открыть приложение из Flutter приложение?)
- намерение андроида
- share plugin
Маршрут № 3-это не совсем то, что я хотел, так как он использует механизм "share" платформы (т. е. сообщение в Twitter / отправить в контакт), вместо того, чтобы открыть полезную нагрузку.
Маршрут 1 и 2 вроде как сработал... дрожащим, странным образом. Я объясню позже.
Вот поток моего кода:
import 'package:url_launcher/url_launcher.dart';
// ...
// retrieve payload from internet and save it to an External Storage location
File payload = await getPayload();
String uriToShare = samplePayload.uri.toString();
// at this point uriToShare looks like: 'file:///storage/emulated/0/jpg_example.jpg'
uriToShare = uriToShare.replaceFirst("file://", "content://");
// launch url
if (await canLaunch(uriToShare)) {
await launch(uriToShare);
} else {
throw "Failed to launch $uriToShare";
Приведенный выше код использовал плагин url_launcher. Если я использовал плагин android_intent, то последние строки кода становится:
// fire intent
AndroidIntent intent = AndroidIntent(
action: "action_view",
data: uriToShare,
);
await intent.launch();
Все, вплоть до сохранения файла во внешний каталог, работает (я могу подтвердить, что файлы существуют после запуска кода)
Все становится странным, когда я пытаюсь поделиться URI.
Я протестировал этот фрагмент кода на 3 разных телефонах. Один из них (Samsung Galaxy S9) выбросил бы это исключение:
I/io.flutter.plugins.androidintent.AndroidIntentPlugin(10312): Sending intent Intent { act=android.intent.action.VIEW dat=content:///storage/emulated/0/jpg_example.jpg }
E/MethodChannel#plugins.flutter.io/android_intent(10312): Failed to handle method call
E/MethodChannel#plugins.flutter.io/android_intent(10312): java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.VIEW dat=content:///storage/emulated/0/jpg_example.jpg cmp=com.google.android.gm/.browse.TrampolineActivity } from ProcessRecord{6da6f74 10312:com.safe.fmeexpress/u0a218} (pid=10312, uid=10218) requires com.google.android.gm.permission.READ_GMAIL
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.os.Parcel.readException(Parcel.java:1959)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.os.Parcel.readException(Parcel.java:1905)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.app.IActivityManager$Stub$Proxy.startActivity(IActivityManager.java:4886)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.app.Instrumentation.execStartActivity(Instrumentation.java:1617)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.app.Activity.startActivityForResult(Activity.java:4564)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.app.Activity.startActivityForResult(Activity.java:4522)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.app.Activity.startActivity(Activity.java:4883)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.app.Activity.startActivity(Activity.java:4851)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at io.flutter.plugins.androidintent.AndroidIntentPlugin.onMethodCall(AndroidIntentPlugin.java:141)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:191)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at io.flutter.view.FlutterNativeView.handlePlatformMessage(FlutterNativeView.java:152)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.os.MessageQueue.nativePollOnce(Native Method)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.os.MessageQueue.next(MessageQueue.java:325)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.os.Looper.loop(Looper.java:142)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at android.app.ActivityThread.main(ActivityThread.java:6938)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
E/MethodChannel#plugins.flutter.io/android_intent(10312): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Я понятия не имею, как намерение было загрязнено
cmp=com.google.android.gm/.browse.TrampolineActivity Это исключение происходит только в Галактике S9.
Два других телефона не дали мне этого проблема.
Они запустили файл uri, и меня спросили, Как открыть файл, но ни одно из приложений для обработки изображений не предлагалось (например, Gallery, QuickPic или Google Photos).
Просто чтобы уточнить, оба маршрута
url_launcher и android_intent приводят к одним и тем же результатам. Такое чувство,что я пропустил шаг. Может ли кто-нибудь указать, что я делаю не так? Должен ли я начать использовать каналы платформы для достижения этой цели?
Некоторые разъяснения о том, почему я сделал то, что я сделал. сделал:
- преобразовал тип uri из file: / / в content: / / потому что я получал
android.os.FileUriExposedException
- сохраненный временный файл во внешнем хранилище, потому что я пока не хочу иметь дело с предоставлением разрешения на чтение URI. (Я пытался, но
android_intentпока не умеет устанавливать флаги намерений)
1 ответ:
Трудно заставить это работать правильно. Вот некоторые подсказки, которые помогли мне запустить
ACTION_VIEWintents из Flutter, с файлами, загруженными с Flutter.1) зарегистрируйте
FileProviderв манифесте android:<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.myapp.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider>
provider_paths.xml:<?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="." /> </paths>2) Создайте пользовательский канал платформы, который предоставляет 2 метода (код ниже-Kotlin):
getDownloadsDir: следует вернуть данные dir, где загруженные файлы должны быть размещены. Попробуйте вот это:val downloadsDir = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).path result.success(downloadsDir);
previewFileэто занимает 2 строковых аргумента:path(File.pathв Dart) иtype(например, "application / pdf"):val file = File(path); val uri = FileProvider.getUriForFile(this, "com.example.myapp.provider", file); val viewFileIntent = Intent(Intent.ACTION_VIEW); viewFileIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION); viewFileIntent.setDataAndType(uri, type); try { startActivity(viewFileIntent); result.success(null); } catch (e: ActivityNotFoundException) { result.error(...); }Наиболее важной частью является создание
FileProvider. url_launcher и android_intent не будут работать, вы должны создать свой собственный канал платформы. Вы можете изменить путь загрузки, но тогда вы также должны найти правильные настройки поставщика/разрешений.Сделать эту работу на iOS также возможно, но вне сферы этого вопроса.
Если вы используете плагин image_picker:
FileProviderконфликтует с текущей версией плагина image_picker (0.4.6), исправление будет выпущено в ближайшее время.
Comments