Java 来自内部存储器的电子邮件

Java 来自内部存储器的电子邮件,java,android,email,Java,Android,Email,在我的应用程序中,我向内部存储器写入一个文件,如中所述。然后,稍后我希望通过电子邮件将我写入的文件发送到内部存储器中。这是我的代码和我得到的错误,任何帮助将不胜感激 FileOutputStream fos = openFileOutput(xmlFilename, MODE_PRIVATE); fos.write(xml.getBytes()); fos.close(); Intent intent = new Intent(android.content.Intent.ACTION_SEND

在我的应用程序中,我向内部存储器写入一个文件,如中所述。然后,稍后我希望通过电子邮件将我写入的文件发送到内部存储器中。这是我的代码和我得到的错误,任何帮助将不胜感激

FileOutputStream fos = openFileOutput(xmlFilename, MODE_PRIVATE);
fos.write(xml.getBytes());
fos.close();
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
...
Uri uri = Uri.fromFile(new File(xmlFilename));
intent.putExtra(android.content.Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(intent, "Send eMail.."));
错误是

文件://附件路径必须指向file://mnt/sdcard. 忽略附件file://...


错误非常具体:您应该使用file from来制作附件。

我认为您可能在android Gmail客户端中发现了一个bug(或者至少是不必要的限制)。我能够解决这个问题,但我觉得它太具体于实现,需要更多的工作才能移植:

First Commonware在需要使文件世界可读方面非常正确:

fos = openFileOutput(xmlFilename, MODE_WORLD_READABLE);
接下来,我们需要解决Gmail坚持使用/mnt/sdcard(或特定于实现的等效物?)路径的问题:


至少在我改进过的姜饼设备上,这让我可以用Gmail从私人存储向自己发送附件,并在收到附件时使用预览按钮查看内容。但是我对必须这样做才能使它工作感到不太“好”,谁知道另一个版本的Gmail、另一个电子邮件客户端或将外部存储安装到其他地方的手机会发生什么呢。

克里斯·斯特拉顿提出了一个好的解决办法。然而,它在很多设备上都失败了。您不应该硬编码/mnt/sdcard路径。你最好计算一下:

String sdCard = Environment.getExternalStorageDirectory().getAbsolutePath();
Uri uri = Uri.fromFile(new File(sdCard + 
          new String(new char[sdCard.replaceAll("[^/]", "").length()])
                    .replace("\0", "/..") + getFilesDir() + "/" + xmlFilename));

考虑到这里的建议:,自API 17以来,我们被鼓励使用ContentProviders等。 多亏了那个家伙和他的帖子,我们有了一个解决方案:

public class CachedFileProvider extends ContentProvider {
public static final String AUTHORITY = "com.yourpackage.gmailattach.provider";
private UriMatcher uriMatcher;
@Override
public boolean onCreate() {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(AUTHORITY, "*", 1);
    return true;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    switch (uriMatcher.match(uri)) {
        case 1:// If it returns 1 - then it matches the Uri defined in onCreate
            String fileLocation = AppCore.context().getCacheDir() + File.separator +     uri.getLastPathSegment();
            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation),     ParcelFileDescriptor.MODE_READ_ONLY);
            return pfd;
        default:// Otherwise unrecognised Uri
            throw new FileNotFoundException("Unsupported uri: " + uri.toString());
    }
}
@Override public int update(Uri uri, ContentValues contentvalues, String s, String[] as) { return     0; }
@Override public int delete(Uri uri, String s, String[] as) { return 0; }
@Override public Uri insert(Uri uri, ContentValues contentvalues) { return null; }
@Override public String getType(Uri uri) { return null; }
@Override public Cursor query(Uri uri, String[] projection, String s, String[] as1, String s1) {     return null; }
}
然后在内部缓存中创建文件:

    File tempDir = getContext().getCacheDir();
    File tempFile = File.createTempFile("your_file", ".txt", tempDir);
    fout = new FileOutputStream(tempFile);
    fout.write(bytes);
    fout.close();
设置意图:

...
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + tempFile.getName()));
并在AndroidManifest文件中注册内容提供程序:

<provider android:name="CachedFileProvider" android:authorities="com.yourpackage.gmailattach.provider"></provider>

如果要使用内部存储,请尝试使用准确的存储路径:

Uri uri = Uri.fromFile(new File(context.getFilesDir() + File.separator + xmlFilename));
或者在调试器中继续更改文件名,只需对每个文件调用“new file(blah).exists()”,即可快速查看文件的确切名称

这也可能是特定于您的设备的实际设备实现问题。您是否尝试过使用其他设备/模拟器

File.setReadable(true, false);

为我工作。

我最近一直在努力解决这个问题,我想分享我从支持库中找到的解决方案。它是内容提供商的一个扩展,可以很好地解决这个问题,而无需解决,而且工作量也不太大

如链接中所述,要激活内容提供商: 在您的清单中,写下:

<application
    ....
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.youdomain.yourapp.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
    ...
在res/xml/file_paths.xml元数据中路径中的任何文件上

现在只需使用:

    Intent mailIntent = new Intent(Intent.ACTION_SEND);
    mailIntent.setType("message/rfc822");
    mailIntent.putExtra(Intent.EXTRA_EMAIL, recipients);

    mailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
    mailIntent.putExtra(Intent.EXTRA_TEXT, body);
    mailIntent.putExtra(Intent.EXTRA_STREAM, contentUri);

    try {
        startActivity(Intent.createChooser(mailIntent, "Send email.."));
    } catch (android.content.ActivityNotFoundException ex) {
        Toast.makeText(this, R.string.Message_No_Email_Service, Toast.LENGTH_SHORT).show();
    }
您不需要授予权限,而是在将url附加到文件时自动授予权限

而且你不需要让你的文件模式_WORLD _可读,这个模式现在已经不推荐了,让它成为模式_PRIVATE,内容提供者为同一个文件创建新的url,其他应用程序可以访问它


我应该注意的是,我只在Gmail模拟器上测试过它。

或者将
MODE\u PRIVATE
更改为
MODE\u WORLD\u READABLE
。由于您没有编写电子邮件程序,它无法读取您的私人文件。谢谢您的回答。我可以读到,但我开发了一个应用程序,用于没有外部存储的手机,因为手机有32GB的内置存储。然后使用@commonware comment:MODE_WORLD_READABLE可以帮你省钱。@杰克逊先生:“没有外部存储的手机,因为手机有32GB的内置存储”——你的设备有外部存储。“外部存储”不是指“SD卡”,而是指“可从主机PC访问”。“File()构造函数不处理权限”--如果您按照我前面评论的第一句话中非常简单的说明(“或将
MODE\u PRIVATE
更改为
MODE\u WORLD\u READABLE
”),则不必处理权限但仍然不行。只要创建URI的文件对象设置为Readable,我就可以使用Gmail和文件提供商从内部存储发送文件,但不适用于Android上的其他邮件应用程序(如邮箱)。我认为far.be下面的内容提供商解决方案是最“正确”的方法。在Android体系结构中,内容提供商确实是公开其他私有文件的预期解决方案。不要忘记添加
Android:grantUriPermissions=“true”
致提供商。请记住,这是一种直接克服某个特定目标程序的作者的特定奇怪期望的黑客行为。今天,我们应该考虑一下内容提供商和其他答案中讨论的类似方法,这些方法在风格上更具“安卓”特色,在实用性上更具普遍性。看起来效果不错。到目前为止,我遇到的这个(非常恼人的)问题是我见过的大多数elegang解决方案。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path path="" name="document"/>
</paths>
Uri contentUri = FileProvider.getUriForFile(context, "com.yourdomain.yourapp.fileprovider", file);
    Intent mailIntent = new Intent(Intent.ACTION_SEND);
    mailIntent.setType("message/rfc822");
    mailIntent.putExtra(Intent.EXTRA_EMAIL, recipients);

    mailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
    mailIntent.putExtra(Intent.EXTRA_TEXT, body);
    mailIntent.putExtra(Intent.EXTRA_STREAM, contentUri);

    try {
        startActivity(Intent.createChooser(mailIntent, "Send email.."));
    } catch (android.content.ActivityNotFoundException ex) {
        Toast.makeText(this, R.string.Message_No_Email_Service, Toast.LENGTH_SHORT).show();
    }