通过 FileProvider 和 Intent 将缓存文件附加到 GMail 不起作用

Attaching cache file to GMail through FileProvider and Intent not working

因此,在过去的一天里,我一直在用头撞墙,试图找出为什么文件无法附加到电子邮件中。每次应用程序 运行s,我都会收到一条提示信息,提示“无法附加文件”。 “收件人”和“主题”字段的填写与我预期的一样。第一个问题是,我怎样才能找到这个错误背后的更多信息?此消息是从 GMail 应用程序而非我自己的程序中抛出的。它肯定会给我指明正确的方向,所以如果我有这个错误的原因,我可以自己进一步调试。我在下面包含了可能相关的信息。文件大小为 78.5kb,我可以验证该文件是否存在以及是否包含我想要附加的正确内容。根据文件资源管理器,文件的所有者和组的权限为 rw。

我在打字时发现了一些有趣的东西 post;当使用文件资源管理器在 gmail 应用程序中添加附件时,Android/data 目录不是一个选项!但是,当您在 gmail 应用程序之外使用文件资源管理器时,它会出现。所以我想这可能是一个权限问题?它无法访问此文件夹。如果是这样,那么推荐的存储此文件的位置是什么?在这种情况下,理想情况下它将是某种缓存位置或临时文件位置。

我已经尝试在 Android Outlook 应用程序而不是 GMail 中添加附件,并且确实附加了文件。

正在尝试 运行 Android 11 API 30 使用 Pixel 4 模拟器。

电子邮件意图代码:

protected void sendEmail(File f){
    final String[] TO = { "foo@bar.com" };

    Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
    emailIntent.setData(Uri.parse("mailto:"));
    emailIntent.putExtra(Intent.EXTRA_EMAIL, TO);
    emailIntent.putExtra(Intent.EXTRA_SUBJECT, f.getName().replaceAll("(?i).pdf", ""));

    if (!f.exists() || !f.canRead()) {
        Toast.makeText(this, "Attachment Error", Toast.LENGTH_SHORT).show();
        finish();
        return;
    }

    emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    Uri uri = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID, f);
    emailIntent.putExtra(Intent.EXTRA_STREAM, uri);

    try {
        startActivity(emailIntent);
        finish();
    } catch (android.content.ActivityNotFoundException ex) {
        Toast.makeText(MainActivity.this,
                "There is no email client installed.", Toast.LENGTH_SHORT).show();
    }

AndroidManifest.xml:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}"
    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 xmlns:android="http://schemas.android.com/apk/res/android">
    <external-cache-path name="external_cache" path="."/>
</paths>

f 的值:

/storage/emulated/0/Android/data/com.bar.foo/cache/Form2020-12-27.pdf

uri 路径的值:

content://com.bar.foo/external_cache/Form2020-12-27.pdf

改用ACTION_SEND

TO也会在这里实现。

添加:

emailIntent.setType("message/rfc822");

您可以删除 setData() 调用;

经过进一步的 futzing,我找到了“解决方案”。它在某些情况下可能不适用,但我所追求的结果恰好有效。它与意图数据和类型有关。我已将意图设置更改为以下内容,并保持所有其他项目不变:

protected void sendEmail(File f){
    final String[] TO = { "foo@bar.com" };

    Intent emailIntent = new Intent(Intent.ACTION_SEND);
    emailIntent.putExtra(Intent.EXTRA_EMAIL, TO);
    emailIntent.putExtra(Intent.EXTRA_SUBJECT, f.getName().replaceAll("(?i).pdf", ""));

    if (!f.exists() || !f.canRead()) {
        Toast.makeText(this, "Attachment Error", Toast.LENGTH_SHORT).show();
        finish();
        return;
    }

    Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID, f);
    emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    emailIntent.setDataAndType(uri, getContentResolver().getType(uri));
    emailIntent.putExtra(Intent.EXTRA_STREAM, uri);

    try {
        startActivity(emailIntent);
        finish();
    } catch (android.content.ActivityNotFoundException ex) {
        Toast.makeText(MainActivity.this,
                "There is no email client installed.", Toast.LENGTH_SHORT).show();
    }
}

我将 Intent 操作从 ACTION_SENDTO 更改为 ACTION_SEND 仅此一项并没有解决问题。

我没有使用“mailto:”手动设置意图数据和类型,而是从 uri 设置数据和类型。

如果未设置默认应用程序,将弹出一个选择器,询问用户要将哪些相关应用程序与该文件一起使用。不过,这不仅仅显示了电子邮件客户端,这对于某些应用程序来说可能并不理想。这并不是我设计的初衷,但考虑到我的用户可能希望将表单附加到非电子邮件客户端(例如 MS Teams 或云存储客户端),它会起作用。单击 GMail 会将文件附加到电子邮件,并按预期设置主题和收件人。

由于支持不同的设备,我遇到了一些问题。这是我想出的解决方案:

Intent intent = new Intent(Intent.ACTION_SEND);
Uri data = Uri.parse("mailto:" + Uri.encode(mailAddress)
                               + "?subject=" + Uri.encode(subject)
                               + "&body=" + Uri.encode(message));
intent.setData(data);
// yes this is duplicate intended code but this ensures more compatibility for different devices
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{mailAddress});
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, message);

String filePath = BaseApplication.getAppContext().getFilesDir().toString() + "/" + FILENAME;

File attachment = new File(filePath);
attachment.setReadable(true, false);

try {
   Uri u = FileProvider.getUriForFile(this,
                                      Constants.FILE_PROVIDER_AUTHORITY,
                                      attachment);
   intent.putExtra(Intent.EXTRA_STREAM, u);

   intent.setType("message/rfc822");

   intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

} catch (IllegalArgumentException e) {
   Log.e(TAG, "Error while providing attachment: " + attachment, e);
}

startActivity(intent);

另外需要一个 FileProvider,就像问题中已经提到的那样。

最终对提问者来说有点晚了,但希望它能帮助其他人!