问题
I have looked at many, many SO posts to find an answer, but no luck...
How do I send a file from public storage as an e-mail attachment?
Most posts are asking about internal storage files. So for that, they use FileProvider
(or something like that). In the docs for FileProvider
, I read:
FileProvider ... facilitates secure sharing of files associated with an app...
But the file I want to send is not associated with my app. It is a .csv file in public storage in the Documents folder. So it seems really bizarre for my app to set up a FileProvider with access permissions for a public file. Even more strange to use my package name as the "authority".
I tried the following code, which I put together based on answers in 1 or 2 other posts:
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File f = new File(path, _sFileName);
_sFileDir = f.getCanonicalPath();
and in another method I have:
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);
This causes the app to crash with the message: "MyApp has stopped"
When I changed "file:/" to "content:/" the app no longer crashed, and the email was sent, but no file was attached.
My AndroidManifest.xml contains the following permissions:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Here is my LogCat from the time when I use "content:/":
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
Here's another LogCat when "file:/" is used & the app crashes:
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)
What is the right way to do this?
New Information:
When I used "content:/", this time I saw a message (Toast?) flash briefly on the Gmail app, saying "Can't attach empty file". But it's not empty!!!
More New Information:
I tried using FileProvider to do this. Here's the code...
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>
filepaths.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);
}
The problem now is that it doesn't work - my app crashes again on the call to FileProvider.getUriForFile()
.
Here's the new 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)
As you can see, there is an InvocationTargetException, but I don't know why...
Additional Information: Replacing MainActivity.this with v.getContext() in FileProvider.getUriForFile() gives the same result.
回答1:
So it seems really bizarre for my app to set up a FileProvider with access permissions for a public file
The app that you are trying to send it through may not have external storage access. Hence, FileProvider
is still the recommended pattern.
This causes the app to crash with the message: "MyApp has stopped"
Use LogCat to examine the Java stack trace associated with the crash in whatever app "MyApp" is.
Also, please note that plain/text
is not a valid MIME type. Presumably you mean text/plain
.
And, given a File
, use Uri.fromFile()
to generate a Uri
for it. Bear in mind that your app may crash on Android 7.0+ for using a file
scheme, though, which is another reason for using FileProvider
.
What is the right way to do this?
Use FileProvider
.
来源:https://stackoverflow.com/questions/45382363/android-send-public-storage-file-via-email