【问题标题】:android send public storage file via emailandroid通过电子邮件发送公共存储文件
【发布时间】:2017-07-28 21:01:45
【问题描述】:

我查看了很多很多 SO 帖子以找到答案,但没有运气......

如何将公共存储中的文件作为电子邮件附件发送?

大多数帖子都在询问内部存储文件。因此,他们使用FileProvider(或类似的东西)。在FileProvider 的文档中,我读到:

FileProvider ...促进与应用程序关联的文件的安全共享...

但我要发送的文件与我的应用没有关联。它是 Documents 文件夹中公共存储中的 .csv 文件。因此,我的应用程序设置一个具有公共文件访问权限的 FileProvider 似乎真的很奇怪。更奇怪的是使用我的包名作为“权限”。

我尝试了以下代码,这些代码是根据其他 1 或 2 个帖子中的答案汇总而成的:

File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File f = new File(path, _sFileName);
_sFileDir = f.getCanonicalPath();

我有另一种方法:

Intent i = new Intent(android.content.Intent.ACTION_SEND);
String Email[] = { "my.name@gmail.com" };
i.putExtra(android.content.Intent.EXTRA_EMAIL, Email);
i.putExtra(android.content.Intent.EXTRA_SUBJECT, "My Report");
i.setType("plain/text");
String sURI = "file:/" + _sFileDir;
i.putExtra(Intent.EXTRA_STREAM, Uri.parse(sURI));
startActivity(i);

这会导致应用崩溃并显示以下消息:“MyApp 已停止”

当我将“file:/”更改为“content:/”时,应用程序不再崩溃,并且发送了电子邮件,但没有附加文件。

我的 AndroidManifest.xml 包含以下权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

这是我使用“content:/”时的 LogCat:

07-28 17:19:22.528 27809-27809/? I/art: Late-enabling -Xcheck:jni
07-28 17:19:22.569 27809-27816/? E/art: Failed sending reply to debugger: Broken pipe
07-28 17:19:22.569 27809-27816/? I/art: Debugger is no longer active
07-28 17:19:22.569 27809-27816/? I/art: Starting a blocking GC Instrumentation
07-28 17:19:22.582 27809-27809/? W/ActivityThread: Application com.myorg.myapp is waiting for the debugger on port 8100...
07-28 17:19:22.583 27809-27809/? I/System.out: Sending WAIT chunk
07-28 17:19:24.636 27809-27816/com.myorg.myapp I/art: Debugger is active
07-28 17:19:24.785 27809-27809/com.myorg.myapp I/System.out: Debugger has connected
07-28 17:19:24.785 27809-27809/com.myorg.myapp I/System.out: waiting for debugger to settle...
07-28 17:19:24.985 27809-27809/com.myorg.myapp I/System.out: waiting for debugger to settle...
07-28 17:19:25.186 27809-27809/com.myorg.myapp I/System.out: waiting for debugger to settle...
07-28 17:19:25.386 27809-27809/com.myorg.myapp I/System.out: waiting for debugger to settle...
07-28 17:19:25.588 27809-27809/com.myorg.myapp I/System.out: waiting for debugger to settle...
07-28 17:19:25.788 27809-27809/com.myorg.myapp I/System.out: waiting for debugger to settle...
07-28 17:19:25.989 27809-27809/com.myorg.myapp I/System.out: waiting for debugger to settle...
07-28 17:19:26.189 27809-27809/com.myorg.myapp I/System.out: debugger has settled (1454)
07-28 17:19:26.408 27809-27809/com.myorg.myapp W/System: ClassLoader referenced unknown path: /data/app/com.myorg.myapp-1/lib/arm
07-28 17:19:26.444 27809-27809/com.myorg.myapp I/InstantRun: starting instant run server: is main process
07-28 17:19:26.508 27809-27816/com.myorg.myapp I/art: Starting a blocking GC Instrumentation
07-28 17:19:26.801 27809-27809/com.myorg.myapp W/art: Before Android 4.1, method android.graphics.PorterDuffColorFilter android.support.graphics.drawable.VectorDrawableCompat.updateTintFilter(android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable
07-28 17:19:28.471 27809-27814/com.myorg.myapp I/art: Do partial code cache collection, code=14KB, data=23KB
07-28 17:19:28.471 27809-27814/com.myorg.myapp I/art: After code cache collection, code=14KB, data=23KB
07-28 17:19:28.471 27809-27814/com.myorg.myapp I/art: Increasing code cache capacity to 128KB
07-28 17:19:28.475 27809-27814/com.myorg.myapp I/art: Do partial code cache collection, code=14KB, data=42KB
07-28 17:19:28.476 27809-27814/com.myorg.myapp I/art: After code cache collection, code=14KB, data=42KB
07-28 17:19:28.476 27809-27814/com.myorg.myapp I/art: Increasing code cache capacity to 256KB
07-28 17:19:29.036 27809-27814/com.myorg.myapp I/art: Do full code cache collection, code=119KB, data=108KB
07-28 17:19:29.037 27809-27814/com.myorg.myapp I/art: After code cache collection, code=113KB, data=86KB
07-28 17:19:29.216 27809-27895/com.myorg.myapp I/Adreno: QUALCOMM build                   : 7d18700, I8ee426a9a2
                                                                       Build Date                       : 10/07/16
                                                                       OpenGL ES Shader Compiler Version: XE031.09.00.03
                                                                       Local Branch                     : mybranch22308589
                                                                       Remote Branch                    : quic/LA.BR.1.3.6_rb1.6
                                                                       Remote Branch                    : NONE
                                                                       Reconstruct Branch               : NOTHING
07-28 17:19:29.229 27809-27895/com.myorg.myapp I/OpenGLRenderer: Initialized EGL, version 1.4
07-28 17:19:29.229 27809-27895/com.myorg.myapp D/OpenGLRenderer: Swap behavior 1
07-28 17:19:29.388 27809-27814/com.myorg.myapp I/art: Do partial code cache collection, code=125KB, data=105KB
07-28 17:19:29.388 27809-27814/com.myorg.myapp I/art: After code cache collection, code=125KB, data=105KB
07-28 17:19:29.388 27809-27814/com.myorg.myapp I/art: Increasing code cache capacity to 512KB
07-28 17:19:29.691 27809-27809/com.myorg.myapp W/art: Before Android 4.1, method int android.support.v7.widget.ListViewCompat.lookForSelectablePosition(int, boolean) would have incorrectly overridden the package-private method in android.widget.ListView
07-28 17:19:29.701 27809-27809/com.myorg.myapp I/Choreographer: Skipped 97 frames!  The application may be doing too much work on its main thread.
07-28 17:19:34.479 27809-27809/com.myorg.myapp I/Choreographer: Skipped 124 frames!  The application may be doing too much work on its main thread.
07-28 17:19:37.417 27809-27809/com.myorg.myapp W/IInputConnectionWrapper: reportFullscreenMode on inexistent InputConnection
07-28 17:19:37.417 27809-27809/com.myorg.myapp W/IInputConnectionWrapper: finishComposingText on inactive InputConnection
07-28 17:19:41.841 27809-27814/com.myorg.myapp I/art: Do full code cache collection, code=252KB, data=246KB
07-28 17:19:41.844 27809-27814/com.myorg.myapp I/art: After code cache collection, code=248KB, data=216KB
07-28 17:19:45.257 27809-27809/com.myorg.myapp I/Choreographer: Skipped 204 frames!  The application may be doing too much work on its main thread.
07-28 17:19:49.125 27809-27809/com.myorg.myapp W/IInputConnectionWrapper: reportFullscreenMode on inexistent InputConnection
07-28 17:19:49.126 27809-27809/com.myorg.myapp W/IInputConnectionWrapper: finishComposingText on inactive InputConnection
07-28 17:19:57.498 27809-27814/com.myorg.myapp I/art: Do partial code cache collection, code=250KB, data=227KB
07-28 17:19:57.499 27809-27814/com.myorg.myapp I/art: After code cache collection, code=250KB, data=227KB
07-28 17:19:57.499 27809-27814/com.myorg.myapp I/art: Increasing code cache capacity to 1024KB
07-28 17:20:12.117 27809-27809/com.myorg.myapp W/IInputConnectionWrapper: reportFullscreenMode on inexistent InputConnection
07-28 17:20:12.117 27809-27809/com.myorg.myapp W/IInputConnectionWrapper: finishComposingText on inactive InputConnection

这是另一个使用“file:/”且应用崩溃时的 LogCat:

07-28 17:43:57.190 30651-30651/com.myorg.myapp D/AndroidRuntime: Shutting down VM
07-28 17:43:57.232 30651-30651/com.myorg.myapp E/AndroidRuntime: FATAL EXCEPTION: main
 Process: com.myorg.myapp, PID: 30651
 java.lang.IllegalStateException: Could not execute method for android:onClick
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
    at android.view.View.performClick(View.java:5612)
    at android.view.View$PerformClick.run(View.java:22285)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6123)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
  Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
    at android.view.View.performClick(View.java:5612) 
    at android.view.View$PerformClick.run(View.java:22285) 
    at android.os.Handler.handleCallback(Handler.java:751) 
    at android.os.Handler.dispatchMessage(Handler.java:95) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6123) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) 
  Caused by: android.os.FileUriExposedException: file://storage/emulated/0/Documents/Tasks.csv exposed beyond app through ClipData.Item.getUri()
    at android.os.StrictMode.onFileUriExposed(StrictMode.java:1813)
    at android.net.Uri.checkFileUriExposed(Uri.java:2360)
    at android.content.ClipData.prepareToLeaveProcess(ClipData.java:832)
    at android.content.Intent.prepareToLeaveProcess(Intent.java:8957)
    at android.content.Intent.prepareToLeaveProcess(Intent.java:8942)
    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1583)
    at android.app.Activity.startActivityForResult(Activity.java:4228)
    at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:50)
    at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:79)
    at android.app.Activity.startActivityForResult(Activity.java:4187)
    at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:859)
    at android.app.Activity.startActivity(Activity.java:4515)
    at android.app.Activity.startActivity(Activity.java:4483)
    at com.myorg.myapp.MainActivity.onClickSend(ProgSummary.java:209)
    at java.lang.reflect.Method.invoke(Native Method) 
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
    at android.view.View.performClick(View.java:5612) 
    at android.view.View$PerformClick.run(View.java:22285) 
    at android.os.Handler.handleCallback(Handler.java:751) 
    at android.os.Handler.dispatchMessage(Handler.java:95) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6123) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)

这样做的正确方法是什么?

新信息:

当我使用“content:/”时,这一次我在 Gmail 应用程序上看到一条消息(Toast?)短暂闪烁,说“无法附加空文件”。但它不是空的!!!

更多新信息:

我尝试使用 FileProvider 来执行此操作。这是代码...

AndroidManifest.xml:

<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.myorg.myapp.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>

文件路径.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="publicdocuments/" path="documents/" />
</paths>

MainActivity.java:

public void onClickSend(View v) {
    Intent i = new Intent(android.content.Intent.ACTION_SEND);
    String Email[] = { "my.address@gmail.com" };
    i.putExtra(android.content.Intent.EXTRA_EMAIL, Email);
    i.putExtra(android.content.Intent.EXTRA_SUBJECT, "Report");
    i.setType("text/plain");
    File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
    File f = new File(path, "file.csv");
    Uri sUri = FileProvider.getUriForFile(MainActivity.this, "com.myorg.myapp.fileprovider", f);
    i.setData(sUri);
    i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    startActivity(i);
}

现在的问题是它不起作用 - 我的应用在调用 FileProvider.getUriForFile() 时再次崩溃。

这是新的 LogCat:

  --------- beginning of crash
07-29 13:45:27.116 10397-10397/com.myorg.myapp E/AndroidRuntime: FATAL EXCEPTION: main
  Process: com.myorg.myapp, PID: 10397
  java.lang.IllegalStateException: Could not execute method for android:onClick
      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
      at android.view.View.performClick(View.java:5612)
      at android.view.View$PerformClick.run(View.java:22285)
      at android.os.Handler.handleCallback(Handler.java:751)
      at android.os.Handler.dispatchMessage(Handler.java:95)
      at android.os.Looper.loop(Looper.java:154)
      at android.app.ActivityThread.main(ActivityThread.java:6123)
      at java.lang.reflect.Method.invoke(Native Method)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
   Caused by: java.lang.reflect.InvocationTargetException
      at java.lang.reflect.Method.invoke(Native Method)
      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
      at android.view.View.performClick(View.java:5612) 
      at android.view.View$PerformClick.run(View.java:22285) 
      at android.os.Handler.handleCallback(Handler.java:751) 
      at android.os.Handler.dispatchMessage(Handler.java:95) 
      at android.os.Looper.loop(Looper.java:154) 
      at android.app.ActivityThread.main(ActivityThread.java:6123) 
      at java.lang.reflect.Method.invoke(Native Method) 
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) 
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) 
   Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Documents/Tasks.csv
      at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:711)
      at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:400)
      at com.myorg.myapp.MainActivity.onClickSend(MainActivity.java:214)
      at java.lang.reflect.Method.invoke(Native Method) 
      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
      at android.view.View.performClick(View.java:5612) 
      at android.view.View$PerformClick.run(View.java:22285) 
      at android.os.Handler.handleCallback(Handler.java:751) 
      at android.os.Handler.dispatchMessage(Handler.java:95) 
      at android.os.Looper.loop(Looper.java:154) 
      at android.app.ActivityThread.main(ActivityThread.java:6123) 
      at java.lang.reflect.Method.invoke(Native Method) 
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) 
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)

如您所见,有一个InvocationTargetException,但我不知道为什么......

附加信息:在 FileProvider.getUriForFile() 中将 MainActivity.this 替换为 v.getContext() 会得到相同的结果。

【问题讨论】:

  • 看看 LogCat Stacktrace
  • 我认为您忘记了读取外部存储的使用权限..并检查您是否在应用程序设置中启用了存储权限..
  • 谢谢@mmmatey,我已经更新了我的代码(上面)以包含权限。
  • 好的,到目前为止一切顺利,您可以在这里粘贴 log cat 吗.. 或者尝试从您当前的 sURI 制作文件,并检查文件是否确实存在? "new File(sUri).exists" ..如果返回false,文件不在这里,或路径不正确或权限不允许应用程序使用,请使用此文件..
  • 并且还尝试替换,getCanonicalPath(),只使用getPath()..

标签: android email-attachments


【解决方案1】:

所以我的应用程序设置一个具有公共文件访问权限的 FileProvider 似乎真的很奇怪

您尝试发送它的应用可能没有外部存储访问权限。因此,FileProvider 仍然是推荐的模式。

这会导致应用崩溃并显示以下消息:“MyApp 已停止”

Use LogCat to examine the Java stack trace 与任何应用程序“MyApp”中的崩溃相关。

另外,请注意plain/text 不是有效的 MIME 类型。大概你的意思是text/plain

并且,给定一个File,使用Uri.fromFile() 为其生成一个Uri。请记住,您的应用可能会因使用 file 方案而在 Android 7.0+ 上崩溃,但这是使用 FileProvider 的另一个原因。

这样做的正确方法是什么?

使用FileProvider

【讨论】:

  • 您尝试发送它的应用可能没有外部存储访问权限。我认为 Gmail 不应该有这个问题...
  • 另外,请注意纯文本不是有效的 MIME 类型。大概你的意思是 text/plain。 嗯......奇怪 - 我从另一篇文章的一个例子中得到了这个。无论如何,我已经按照您的建议进行了更改。
  • 使用 FileProvider。 实际上,我在发布此问题之前就开始这样做了,但我停止并撤回了我的更改,因为我看不到前进的方向。所以,在我的清单文件中,我应该有:android:authorities="com.myorg.myapp.fileprovider" 还是什么?同样,我的应用程序是否应该对 Excel 在某人的笔记本电脑上创建的文件具有权限...?
  • @ToddHoatson:“权威”是 IETF 标准术语。不要读太多。
  • FileProvider 的 Android 开发者文档指出:“ 元素必须包含以下一个或多个子元素: " 我假设我需要使用外部路径...对吗?
猜你喜欢
  • 2020-03-01
  • 2014-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-07
  • 1970-01-01
  • 2017-10-21
  • 2012-04-24
相关资源
最近更新 更多