Gmail 5.0 应用程序在收到 ACTION_SEND 意图时失败并显示“附件的权限被拒绝”
Posted
技术标签:
【中文标题】Gmail 5.0 应用程序在收到 ACTION_SEND 意图时失败并显示“附件的权限被拒绝”【英文标题】:Gmail 5.0 app fails with "Permission denied for the attachment" when it receives ACTION_SEND intent 【发布时间】:2015-01-09 02:03:49 【问题描述】:我的应用程序创建带有附件的邮件,并使用带有Intent.ACTION_SEND
的意图来启动邮件应用程序。
它适用于我测试过的所有邮件应用程序,除了新的 Gmail 5.0(它适用于 Gmail 4.9),其中邮件打开时没有附件,显示错误:“附件的权限被拒绝”。
logcat 上没有来自 Gmail 的有用邮件。我只在 android KitKat 上测试了 Gmail 5.0,但在多个设备上测试。
我像这样为附件创建文件:
String fileName = "file-name_something_like_this";
FileOutputStream output = context.openFileOutput(
fileName, Context.MODE_WORLD_READABLE);
// Write data to output...
output.close();
File fileToSend = new File(context.getFilesDir(), fileName);
我知道MODE_WORLD_READABLE
的安全问题。
我这样发送意图:
public static void compose(
Context context,
String address,
String subject,
String body,
File attachment)
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("message/rfc822");
emailIntent.putExtra(
Intent.EXTRA_EMAIL, new String[] address );
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
emailIntent.putExtra(
Intent.EXTRA_STREAM,
Uri.fromFile(attachment));
Intent chooser = Intent.createChooser(
emailIntent,
context.getString(R.string.send_mail_chooser));
context.startActivity(chooser);
在创建文件或发送意图时我做错了什么吗?有没有更好的方法来启动带有附件的邮件应用程序?或者 - 有人遇到过这个问题并找到了解决方法吗?
谢谢!
【问题讨论】:
@nobalG 与此问题无关。 我也遇到了这个问题,附件的位置似乎与问题有关,因为从 sd 卡共享附件仍然有效。 (由于 WORLD_READABLE 标志,这应该无关紧要)这可能是一个错误吗? 同样的问题...我仍然可以使用 ACTION_SEND 将文件发送到 Google Drive,但是当我选择 GMail(使用新版本)时,它会像您看到的那样失败。在以前版本的 GMail 上使用相同的代码没有问题。 在共享本地 PDF 文件时遇到了同样的问题。找到了这个解决方案,使用一个最小的 github 示例项目进行基本的 FileProvider 设置:***.com/a/48103567/2162226,其中添加和发送附件是那里的代码的一部分。它提供了将哪些文件复制(不进行更改)到本地独立项目的步骤 【参考方案1】:我能够通过 Intent 将屏幕截图 .jpeg 文件从我的应用程序传递到 GMail 5.0。密钥在this answer。
我从 @natasky 的代码中获得的所有内容几乎相同,但相反,我将文件目录设置为
context.getExternalCacheDir();
“代表你应该保存缓存文件的外部存储目录”(文档)
【讨论】:
这解决了我的问题。更改为 startActivityForResult 后,我在 Gmail 中收到“无法附加空文件”的错误,即使我可以验证文件不为空。问题是我在“getCacheDir()”目录中创建文件。更改为“getExternalCacheDir()”导致附件成功! 非常感谢,显然这是让它在我的应用程序中运行的关键。 这是为我做的。我实际上使用的是File.createTempFile
,它接受位置的第三个参数。我为第三个参数传递了activity.getExternalCacheDir()
,一切都很顺利。谢谢!【参考方案2】:
GMail 5.0 对其从 Intent 接收的附件添加了一些安全检查。这些与 unix 权限无关,因此文件可读这一事实并不重要。
当附件 Uri 是 file:// 时,它只会接受来自外部存储的文件、gmail 本身的私有目录或来自调用应用的私有数据目录的世界可读文件。
此安全检查的问题在于它依赖于 gmail 能够找到调用者应用程序,这仅在调用者要求结果时才可靠。在上面的代码中,您不要求结果,因此 gmail 不知道调用者是谁,并拒绝您的文件。
由于它在 4.9 中对您有效,但在 5.0 中无效,因此您知道这不是 unix 权限问题,因此原因一定是新的检查。
TL;DR 答案: 将 startActivity 替换为 startActivityForResult。
或者更好的是,使用内容提供者。
【讨论】:
谢谢,使用 startActivityForResult 允许 GMail 创建邮件但无法发送:libcore.io.ErrnoException: open failed: EACCES (Permission denied) at libcore.io.Posix.open (本机方法)。我正在通过 final File tempImage = File.createTempFile("image", ".jpg"); 创建附件文件 当我尝试这个时,我仍然得到一个权限被拒绝的吐司。也许内容提供商是唯一的出路 在这种情况下,它确实是一个文件权限问题。 createTempFile 创建一个只有创建它的 uid 才能读取的文件。不是本页中描述的问题。此页面中描述的问题是该文件是世界可读的,但 gmail 由于其内部检查而拒绝读取它,这就是 startActivityForResult 解决的问题。但如果 gmail 无法读取您的文件,则需要单独处理。 我有一个不再可读的世界可读文件,但是用 startActivityForResult 替换 startActivity 为我解决了这个问题。谢谢! 请提供一些代码sn-p以便我们使用【参考方案3】:将getExternalCacheDir()
与File.createTempFile
一起使用。
使用以下命令在外部缓存目录中创建一个临时文件:
File tempFile = File.createTempFile("fileName", ".txt", context.getExternalCacheDir());
然后将你原来的文件内容复制到tempFile
,
FileWriter fw = new FileWriter(tempFile);
FileReader fr = new FileReader(Data.ERR_BAK_FILE);
int c = fr.read();
while (c != -1)
fw.write(c);
c = fr.read();
fr.close();
fw.flush();
fw.close();
现在将您的文件置于意图中,
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
【讨论】:
Josh 是否愿意详细说明您对此的使用情况?我将我的文件复制到一个临时文件中 @wal 我以两种方式使用它,一种是将文件复制到带有FileUtils.copyFile(file, temporaryFile);
的新创建的临时文件中。另一种方法是创建临时文件并使用缓冲区用数据填充该文件。这有帮助吗?
这只是一种解决方法,不必要地复制数据,占用双倍空间,必须记住清除缓存目录。最好使用 FileProvider。【参考方案4】:
您应该implement a FileProvider,它可以为您应用的内部文件创建 Uris。其他应用程序被授予读取这些 Uris 的权限。然后,您无需调用 Uri.fromFile(attachment),而是实例化您的 FileProvider 并使用:
fileProvider.getUriForFile(attachment);
【讨论】:
【参考方案5】:Google 为该问题提供了 answer:
将数据存储在您自己的ContentProvider
中,确保其他应用程序具有访问您的提供程序的正确权限。提供访问的首选机制是使用per-URI permissions,它是临时的,仅授予接收应用程序的访问权限。像这样创建ContentProvider
的一种简单方法是使用FileProvider
辅助类。
使用系统MediaStore
。 MediaStore
主要针对视频、音频和图像 MIME 类型,但从 Android 3.0(API 级别 11)开始,它还可以存储非媒体类型(有关更多信息,请参阅 MediaStore.Files
)。可以使用scanFile()
将文件插入MediaStore
,然后将适合共享的content://
样式Uri
传递给提供的onScanCompleted()
回调。请注意,一旦将内容添加到系统 MediaStore
,设备上的任何应用都可以访问该内容。
您也可以尝试set permissions 为您的文件:
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
最后,您可以将文件复制/存储在外部存储中 - 那里不需要权限。
【讨论】:
【参考方案6】:我测试了一下,发现肯定是私有存储访问问题。 当您将某些文件附加到 Gmail(超过 5.0)时,请勿使用私人存储中的文件,例如 /data/data/package/。尝试使用 /storage/sdcard。
您可以成功附加文件。
【讨论】:
【参考方案7】:不确定为什么 GMail 5.0 不喜欢某些文件路径(我已经确认它确实具有读取权限),但显然更好的解决方案是实现您自己的 ContentProvider 类来提供文件。它实际上有点简单,我在这里找到了一个不错的例子:http://stephendnicholas.com/archives/974
请务必将标签添加到您的应用清单中,并在其中包含“android:grantUriPermissions="true"”。您还需要实现 getType() 并为文件 URI 返回适当的 MIME 类型,否则某些应用程序将无法使用它...链接的评论部分中有一个示例。
【讨论】:
【参考方案8】:我遇到了这个问题,终于找到了一种简单的方法来发送带有附件的电子邮件。这是代码
public void SendEmail()
try
//saving image
String randomNameOfPic = Calendar.DAY_OF_YEAR+DateFormat.getTimeInstance().toString();
File file = new File(ActivityRecharge.this.getCacheDir(), "slip"+ randomNameOfPic+ ".jpg");
FileOutputStream fOut = new FileOutputStream(file);
myPic.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
file.setReadable(true, false);
//sending email
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]"zohabali5@gmail.com");
intent.putExtra(Intent.EXTRA_SUBJECT, "Recharge Account");
intent.putExtra(Intent.EXTRA_TEXT, "body text");
//Uri uri = Uri.parse("file://" + fileAbsolutePath);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Send email..."),12);
catch (Exception e)
Toast.makeText(ActivityRecharge.this,"Unable to open Email intent",Toast.LENGTH_LONG).show();
在此代码中,“myPic”是相机意图返回的位图
【讨论】:
感谢您的代码。它对我来说很好,但有 Josh Pinter 的注释(见下一个答案)。【参考方案9】:第 1 步:在附加的 URI 中添加权限
Uri uri = FileProvider.getUriForFile(context, ""com.yourpackage", file);
与您的清单文件提供名称相同
android:authorities="com.yourpackage"
第 2 步`;添加允许读取的标志
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
【讨论】:
以上是关于Gmail 5.0 应用程序在收到 ACTION_SEND 意图时失败并显示“附件的权限被拒绝”的主要内容,如果未能解决你的问题,请参考以下文章
Android ACTION_SEND 超链接在 GMail 中不起作用
Android - 对收到的推送通知进行分组,如 Gmail
收到来自 gmail users.watch API 的推送通知后如何阅读用户 gmail?
为啥我在通过 IMAP 连接到 Gmail 时收到“需要 Web 登录”消息?