Flutter: как разрешить файлу быть открытым внешним приложением (например, неявным намерением Android)?



Я создаю приложение Flutter, которое по существу извлекает данные из облака.
Тип данных варьируется, но обычно это изображение, pdf, текстовый файл или архив (zip-файл).



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

Я искал ответы, и я попробовал следующие маршруты:




  1. плагин url_launcher (как предложено в Как открыть приложение из Flutter приложение?)

  2. намерение андроида

  3. 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 пока не умеет устанавливать флаги намерений)

505   1  

1 ответ:

Трудно заставить это работать правильно. Вот некоторые подсказки, которые помогли мне запустить ACTION_VIEW intents из 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

    Ничего не найдено.