首页 文章

Flutter:如何通过外部应用程序打开文件(如Android的隐含意图)?

提问于
浏览
4

我正在构建一个基本上从 Cloud 端获取数据的Flutter应用程序 . 数据类型各不相同,但它们通常是图像,pdf,文本文件或存档(zip文件) .

现在我想激发一个隐含的意图,因此用户可以选择他们喜欢的应用程序来处理有效负载 .

我已经搜索了答案,我尝试了以下路线:

路线#3并不是我想要的,因为它使用平台的“共享”机制(即在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部不同的手机上测试了这段代码 . 其中一个(三星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 污染的

此例外仅发生在Galaxy S9中 . 另外两部手机没有给我这个问题 . 他们确实启动了文件uri,我被问到如何打开文件,但没有提供任何图像处理应用程序(例如,像Gallery,QuickPic或Google Photos) .

只是为了澄清, url_launcherandroid_intent 路线导致完全相同的结果 .

感觉就像我在这里错过了一步 . 谁能指出我做错了什么?我是否必须开始使用平台 Channels 来完成此任务?

对我为什么做我所做的一些澄清:

  • 将uri类型从file://转换为content://因为我收到 android.os.FileUriExposedException

  • 在外部存储中存储临时文件,因为我还不想处理授予URI READ PERMISSION . (我试过,但 android_intent 还没有办法设置意图标志)

1 回答

  • 2

    要让它正常工作很难 . 以下是一些帮助我从Flutter启动 ACTION_VIEW 意图的提示,其中文件是使用Flutter下载的 .

    1)在android清单中注册 FileProvider

    <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 :应返回应放置下载文件的数据目录 . 试试这个:

    val downloadsDir = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).path
    result.success(downloadsDir);
    

    previewFile 需要2个字符串参数: path (Dart中为 File.path )和 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不起作用,你必须创建自己的平台 Channels . 您可以更改下载路径,但是您还必须找到正确的提供者/权限设置 .

    在iOS上进行这项工作也是可能的,但超出了这个问题的范围 .


    如果您使用的是image_picker插件:

    FileProvider 与当前版本的image_picker(0.4.6)插件冲突,修复程序即将发布 .

相关问题