You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Studio中SMTP发送多图附件遇FileNotFoundException求助

解决Android中相册选图作为邮件附件时的FileNotFoundException问题

我帮你排查到问题所在了——你遇到的FileNotFoundException: /external/images/media/39235: open failed: ENOENT (No such file or directory),核心原因是:
通过ACTION_PICK从相册选择图片后拿到的是Content URI(格式类似content://external/images/media/39235),而不是真实的文件系统路径。你直接调用Uri.getPath()得到的字符串并不是可直接访问的文件路径,再加上Android 10(API 29)及以上的Scoped Storage机制限制APP直接访问外部存储文件,自然会提示文件不存在。


核心解决思路

不要尝试通过Content URI获取真实文件路径,而是直接通过ContentResolver打开该URI对应的输入流,用输入流来构建邮件附件的数据源,这样既能适配Scoped Storage,也能兼容所有Android版本。


修改你的代码

1. 修改发送邮件的附件处理逻辑

把原来用FileDataSource的部分替换成基于输入流的数据源实现,这里我们可以用JavaMail的ByteArrayDataSource,或者自定义一个基于输入流的DataSource(如果ByteArrayDataSource不可用的话)。

首先,确保你能拿到有效的Context(比如Activity的实例或者Application Context),然后修改附件循环的代码:

// 替换原来的附件循环部分
if (!arrayUri.isEmpty()) {
    Multipart multipart = new MimeMultipart();
    ContentResolver resolver = getContentResolver(); // 这里要确保是Activity的ContentResolver,或者传入Context获取
    for (Uri uri : arrayUri) {
        MimeBodyPart messageBodyPart2 = new MimeBodyPart();
        
        // 获取文件名:通过ContentResolver查询MediaStore的列来得到真实文件名
        String[] projection = {MediaStore.Images.Media.DISPLAY_NAME};
        Cursor cursor = resolver.query(uri, projection, null, null, null);
        String filename = "unknown_image.jpg";
        if (cursor != null && cursor.moveToFirst()) {
            int nameIndex = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME);
            filename = cursor.getString(nameIndex);
            cursor.close();
        }

        // 从Content URI获取输入流
        try (InputStream inputStream = resolver.openInputStream(uri)) {
            // 用ByteArrayDataSource包装输入流
            DataSource source = new ByteArrayDataSource(inputStream, resolver.getType(uri));
            messageBodyPart2.setDataHandler(new DataHandler(source));
            messageBodyPart2.setFileName(filename);
            messageBodyPart2.setDisposition(MimeBodyPart.ATTACHMENT);
            multipart.addBodyPart(messageBodyPart2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    message.setContent(multipart);
}

2. 权限适配(重要)

  • Android 6.0(API 23)到Android 12(API 32):需要动态申请READ_EXTERNAL_STORAGE权限
  • Android 13(API 33)及以上:需要动态申请READ_MEDIA_IMAGES权限

你需要在代码中添加权限申请逻辑,确保用户授权后再进行选图和发送操作。

3. 选图代码无需修改

你当前的ACTION_PICK选图逻辑是正确的,不需要改动,只要确保拿到的Uri是有效的即可。


额外说明

  • 为什么不直接获取真实文件路径?
    在Android 10及以上的Scoped Storage机制下,APP无法直接访问外部存储的文件路径(除了APP私有目录),即使通过ContentResolver查询到路径,也可能没有访问权限,所以用输入流是最稳妥的方案。
  • 如果你的项目无法引入ByteArrayDataSource,可以自定义一个基于输入流的DataSource:
public class InputStreamDataSource implements DataSource {
    private InputStream inputStream;
    private String contentType;

    public InputStreamDataSource(InputStream inputStream, String contentType) {
        this.inputStream = inputStream;
        this.contentType = contentType;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return inputStream;
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Not supported");
    }

    @Override
    public String getContentType() {
        return contentType;
    }

    @Override
    public String getName() {
        return "InputStreamDataSource";
    }
}

使用时把ByteArrayDataSource替换成这个自定义类即可。

内容的提问来源于stack exchange,提问作者epssy_sy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:14:20