android - Flutter:如何让外部应用程序打开文件(如Android的隐式 Intent )?

标签 android flutter

我正在构建一个从云中获取数据的 Flutter 应用程序。 数据类型各不相同,但通常是图​​像、pdf、文本文件或存档(zip 文件)。

现在我想触发一个隐式 Intent ,以便用户可以选择他们最喜欢的应用来处理有效负载。

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

  1. url_launcher 插件(在 How to open an application from a Flutter app? 中建议)
  2. android Intent
  3. 分享插件

Route #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)

我不知道 Intent 是如何被 cmp=com.google.android.gm/.browse.TrampolineActivity

污染的

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

澄清一下,url_launcherandroid_intent 路由都会导致完全相同的结果。

感觉好像我在这里错过了一步。谁能指出我做错了什么?我是否必须开始使用平台 channel 来完成此操作?

关于我为什么这样做的一些说明:

  • 将 uri 类型从 file://转换为 content://因为我得到 android.os.FileUriExposedException
  • 将临时文件存储在外部存储中,因为我还不想处理授予 URI READ PERMISSION 的问题。 (我试过了,但是 android_intent 还没有办法设置 Intent 标志)

最佳答案

很难让它正常工作。以下是一些提示,可帮助我从 Flutter 启动 ACTION_VIEW Intent ,并使用 Flutter 下载文件。

1) 在android manifest中注册一个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)创建一个自定义平台 channel ,提供2种方法(下面代码为Kotlin):

getDownloadsDir:应返回应放置下载文件的数据目录。试试这个:

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

previewFile 接受 2 个字符串参数:path(Dart 中的 File.path)和 类型(例如“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 将不起作用,您必须创建自己的平台 channel 。您可以更改下载路径,但您还必须找到正确的提供程序/权限设置。

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


如果您使用的是 image_picker 插件:

FileProvider 与 image_picker (0.4.6) 插件的当前版本冲突,很快就会发布修复。

关于android - Flutter:如何让外部应用程序打开文件(如Android的隐式 Intent )?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51885459/

相关文章:

android - 如何使用 connectedAndroidTest 命令只运行一个测试套件

java - 重新打开应用程序时,布局颜色更改不会保留

Android SDK管理器打不开

flutter - 如何在 Xcode 中打开 flutter 插件?

python - TemplateDoesNotExist at/accounts/(Django With Flutter)

android - 如何使用 Html.fromHtml() 从 &lt;style&gt; 标签中删除内容?

flutter - 使用Flutter WEB将内容分享到社交媒体

Flutter 溢出圆圈头像覆盖容器

json - 无需 future/async 同步解析本地 json 文件

android - 用 Espresso 测试订单