Android SMTP 邮件发送实战:源码解析与避坑指南
简介这份Android源码资源聚焦于不依赖系统Mail客户端的邮件发送方案面向需要集成邮件功能的Android开发者尤其适合希望摆脱手机必须安装邮件客户端这一限制的中级学习者。资源核心围绕SMTP协议展开通过引入mail.jar、activation.jar、additionnal.jar等第三方库实现向任意邮箱发送邮件的功能同时保留了调用系统Mail客户端的对比实现便于理解两种方式的差异。压缩包共60个文件约3.81MB包含15个class、11个xml、8个jar、5个java及9个png等涵盖源码、资源布局、依赖库与编译产物结构完整。已有2045人学习下载读者可从中获取SMTP邮件发送的完整工程配置、JavaMail依赖集成方式与AndroidManifest权限设置并参考作者多次尝试后的排错思路快速将邮件发送能力落地到自己的项目中。1. 任意邮箱发信这件事Android 源码里到底藏了什么做过 Android 端邮件功能的同行大概都有过这种体验产品经理一句“支持任意邮箱发送”听起来像是个调个 API 就完事的活真动手才发现坑比想象中深。系统自带的 Intent 只能唤起第三方客户端用户装没装、登没登账号全看运气JavaMail 那套东西又老又重在 Android 上跑起来各种兼容问题。这份 Android 源码给的是一个能直接落地的方案——不依赖用户装任何邮件 App在应用内部完成 SMTP 发信QQ 邮箱、163、Gmail、企业邮、自建邮局都能接。它适合两类人一类是正在做用户反馈、告警通知、验证码下发这类功能的 Android 开发另一类是手里有源码包但不知道怎么把邮件模块拆出来复用的工程师。下面按“这东西怎么跑起来 → 参数怎么配 → 哪里会翻车”的顺序拆一遍。2. SMTP 协议与 JavaMail 在 Android 上的适配逻辑2.1 为什么不用系统 Intent 而选 SMTP 直连Android 发邮件常见三条路Intent 唤起、第三方 SDK、SMTP 直连。Intent 那条路最省事但可控性几乎为零——你没法知道用户到底发出去了没有也没法在后台静默发信。第三方 SDK 通常要联网鉴权、有调用配额小项目犯不上。SMTP 直连的核心优势是完全自主可控发件服务器地址、端口、加密方式、认证信息全在你自己手里发信结果同步返回适合做验证码、告警、日志上报这类需要确定性结果的场景。这份源码走的就是 SMTP 直连路线底层依赖 JavaMail 的 Android 适配版本。原始 JavaMail 是为 J2SE 写的直接扔进 Android 会缺一堆 javax 包所以源码里通常会带一个mail.jar加activation.jar的组合或者用javamail-android这个移植版。判断一个 Android 邮件源码包能不能用第一件事就是看它有没有把这两个依赖处理好——缺了的话运行时报ClassNotFoundException: javax.mail.*这是最常见的翻车点。2.2 源码模块拆解从配置到发送的完整链路一份结构清晰的 Android 邮件源码通常包含四个核心部分配置层MailConfig、会话层Session 构建、消息层MimeMessage 组装、发送层Transport。下面按这个链路把关键代码过一遍。先看配置层它决定了连哪台服务器、用什么加密public class MailConfig { // SMTP 服务器地址不同邮箱服务商不一样 public String host smtp.qq.com; // SSL 加密常用 465STARTTLS 常用 587明文 25 基本已被淘汰 public int port 465; // 发件人完整邮箱地址 public String from your_accountqq.com; // 授权码注意不是登录密码 public String password your_auth_code; // 是否开启 SSL public boolean ssl true; }这里每个字段都有讲究。host填错直接连不上port和ssl必须匹配465 配 SSL、587 配 STARTTLS配错了握手阶段就断password这一项是新手最容易栽的地方——QQ 邮箱、163 邮箱现在都要求用授权码而不是登录密码授权码要在邮箱网页端的设置里单独开启 SMTP 服务后生成。会话层负责把配置转成 JavaMail 的 SessionProperties props new Properties(); props.put(mail.smtp.host, config.host); props.put(mail.smtp.port, String.valueOf(config.port)); props.put(mail.smtp.auth, true); if (config.ssl) { // SSL 工厂缺了这行 465 端口会连接超时 props.put(mail.smtp.socketFactory.class, javax.net.ssl.SSLSocketFactory); props.put(mail.smtp.socketFactory.fallback, false); } Session session Session.getInstance(props, new Authenticator() { Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(config.from, config.password); } });mail.smtp.auth必须为 true否则服务器拒绝认证。SSL 那两行是 465 端口的标配很多人只改了端口没加 socketFactory结果就是连接一直挂着直到超时日志里还看不出明确原因属于典型的玄学问题。消息层组装邮件内容支持纯文本、HTML、附件MimeMessage message new MimeMessage(session); message.setFrom(new InternetAddress(config.from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(targetexample.com)); message.setSubject(测试主题, UTF-8); // 指定编码防中文乱码 // 用 MimeMultipart 支持正文加附件 MimeMultipart multipart new MimeMultipart(); MimeBodyPart textPart new MimeBodyPart(); textPart.setContent(h3正文/h3, text/html;charsetUTF-8); multipart.addBodyPart(textPart); message.setContent(multipart);中文乱码是高频问题根源就在setSubject和setContent没指定 UTF-8。源码里如果这两处漏了编码参数收到的邮件标题就是一串问号。发送层最简单但必须放在子线程new Thread(() - { try { Transport.send(message); } catch (MessagingException e) { // 网络异常、认证失败都会走到这里 e.printStackTrace(); } }).start();Android 主线程不允许网络操作Transport.send是阻塞调用放主线程直接 ANR。源码里如果没做线程封装用之前得自己补上或者换成AsyncTask、线程池。3. 把源码跑起来环境、依赖与最小可运行示例3.1 依赖引入与工程配置拿到源码包后第一步是确认依赖。如果源码用的是本地 jar 方式把mail.jar和activation.jar放进app/libs/然后在build.gradle里声明android { packagingOptions { // 这两个文件在多个 jar 里重复不排除会打包失败 exclude META-INF/LICENSE.txt exclude META-INF/NOTICE.txt } } dependencies { implementation files(libs/mail.jar) implementation files(libs/activation.jar) }packagingOptions那两行是血泪经验——JavaMail 的 jar 里带了 LICENSE 和 NOTICE和 Android 支持库冲突不排除的话编译期直接报Duplicate files copied。如果用 Gradle 远程依赖常见做法是引com.sun.mail:android-mail和com.sun.mail:android-activation版本号以源码里声明的为准别自己乱升。网络权限别忘了Android 9 以后默认禁止明文 HTTP但 SMTP over SSL 不受影响只需要基础权限uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE /3.2 各邮箱服务商的参数对照不同邮箱的 SMTP 参数差异很大这是配置阶段最耗时的部分。下面这张表是常见服务商的对照填配置时直接查邮箱服务SMTP 服务器SSL 端口STARTTLS 端口密码类型QQ 邮箱smtp.qq.com465587授权码163 邮箱smtp.163.com465994授权码Gmailsmtp.gmail.com465587应用专用密码Outlooksmtp.office365.com—587账户密码企业自建邮局由管理员提供常见 465常见 587账户密码注意 Gmail 和 QQ 这类服务光有账号密码不够必须先在网页端开启 SMTP 服务并生成专用凭证。这一步不做代码写得再对也是AuthenticationFailedException。3.3 最小可运行示例与验证方法把配置填好后写一个最小测试跑通链路// 在 Activity 或 ViewModel 里调用 MailConfig config new MailConfig(); config.host smtp.qq.com; config.port 465; config.from 你的QQ号qq.com; config.password 你的授权码; // 不是QQ密码 config.ssl true; MailSender sender new MailSender(config); sender.send(接收方example.com, 测试标题, 测试正文);验证顺序建议这样走先确认能连上服务器日志里出现220 smtp.qq.com之类的欢迎语再确认认证通过出现235 Authentication successful最后确认发送成功出现250 OK。如果卡在第一步查 host 和 port卡在第二步查授权码卡在第三步查收件人地址格式。源码里一般会打这些日志没有的话自己在Transport.send前后加。4. 避坑与排查发信失败的五种典型现场4.1 连接超时或握手失败现象调用发送后长时间无响应最终抛MessagingException: Could not connect to SMTP host。原因端口和加密方式不匹配是最常见的。465 端口必须配 SSL socketFactory587 端口必须调starttls.enable两者搞反了握手阶段就断。其次是网络环境本身屏蔽了 SMTP 端口部分企业网络或公共 WiFi 会封 25、465 端口。解决先核对端口与加密配置是否成对再换网络环境测试如果目标服务器支持多端口优先试 587 STARTTLS这个组合被屏蔽的概率低一些。4.2 认证失败但密码明明是对的现象抛AuthenticationFailedException反复确认密码没输错。原因绝大多数情况是把登录密码当成了 SMTP 密码。QQ、163、Gmail 现在都要求用授权码或应用专用密码登录密码在 SMTP 认证里是无效的。解决登录邮箱网页端找到“账户设置”里的 SMTP 服务开关开启后生成授权码用授权码替换配置里的 password。授权码一般只显示一次记得存好。4.3 中文标题和正文乱码现象邮件能发出去但收件方看到的标题是乱码正文中文变成问号。原因setSubject和setContent没指定字符编码JavaMail 默认用平台编码Android 上和服务端不一致就乱码。解决所有涉及文本的地方强制指定 UTF-8setSubject(subject, UTF-8)、setContent(content, text/html;charsetUTF-8)附件文件名也要用MimeUtility.encodeText处理。4.4 主线程调用导致 ANR现象点击发送按钮后界面卡死几秒后弹出“应用无响应”。原因Transport.send是同步阻塞调用直接放在主线程执行。解决所有发信操作放进子线程、线程池或协程。源码里如果没封装自己包一层同时注意回调结果要切回主线程更新 UI。4.5 打包时报重复文件冲突现象编译期报Duplicate files copied in APK META-INF/LICENSE.txt。原因JavaMail 的 jar 和 Android 支持库都带了同名的 LICENSE、NOTICE 文件。解决在build.gradle的packagingOptions里 exclude 掉冲突文件或者用pickFirst指定保留其中一个。5. 进阶技巧把发信模块做成可复用组件源码跑通只是起点真正省事的是把它抽成一个独立模块。我一般会做三件事把配置抽成外部传入的MailConfig对象支持运行时切换不同发件账号把发送结果封装成回调或LiveData让调用方不用关心线程切换再加一层重试逻辑网络抖动导致的失败自动重试两次。重试这块有个细节值得说不要无脑重试所有异常。认证失败重试多少次都没用只会浪费用户时间连接超时和发送中断才值得重试。判断方式是捕获MessagingException后看 cause 类型AuthenticationFailedException直接返回失败其他情况才进重试队列。private void sendWithRetry(MimeMessage msg, int maxRetry) { int attempt 0; while (attempt maxRetry) { try { Transport.send(msg); return; // 成功直接返回 } catch (AuthenticationFailedException e) { // 认证问题重试无意义直接上报 notifyFail(认证失败请检查授权码); return; } catch (MessagingException e) { attempt; if (attempt maxRetry) { notifyFail(发送失败已重试 maxRetry 次); } } } }另一个实用技巧是附件大小控制。SMTP 服务器对单封邮件大小有限制QQ 邮箱一般 50MB超过直接拒收。发大附件前先算一下 Base64 编码后的体积原始大小乘以 1.37超限就改成分片或走对象存储链接。这个判断放在组装MimeBodyPart之前做能省掉一次无效的网络往返。从那以后我每次接邮件模块都强制先跑一遍最小发送测试确认链路通了再往上叠业务逻辑。很多看起来复杂的问题其实卡在最基础的那一步。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

OmniDrive安全机制揭秘:逆向MT1959的AES-CMAC签名验证与固件完整性绕过

OmniDrive安全机制揭秘:逆向MT1959的AES-CMAC签名验证与固件完整性绕过

【免费下载链接】OmniDrive 项目地址: https://gitcode.com/gh_mirrors/omn/OmniDrive 点击查看 免费下载 OmniDrive 是一款针对 MediaTek MT1959 光驱芯片的固件修改(modding)项目,让 Hitachi-LG 系光驱(LG BU40N、A…

2026/10/15 18:13:25 阅读更多 →
SpringBoot+Vue驾校预约系统开发实战:从数据库设计到并发防超卖

SpringBoot+Vue驾校预约系统开发实战:从数据库设计到并发防超卖

1. 驾校预约到底卡在哪:手工排班的混乱让我决定做这套系统说实话,最初决定做一套基于SpringBootVue的驾校预约学习系统,不是因为我多喜欢写业务代码,而是被某驾校前台那本厚厚的纸质预约本彻底逼疯了。每次学员打电话约车&#xf…

2026/10/15 18:13:25 阅读更多 →
ZeroTermux 内置命令手册精讲:zcat 命令——不真正解压也能查看 gzip 压缩包内容

ZeroTermux 内置命令手册精讲:zcat 命令——不真正解压也能查看 gzip 压缩包内容

移动开发开发工具 【免费下载链接】ZeroTermux 项目地址: https://gitcode.com/GitHub_Trending/ze/ZeroTermux 点击查看 免费下载 本篇围绕 ZeroTermux 内置 Linux 命令手册(app/src/main/assets/cmd/zcat.md)展开,系统讲解 zca…

2026/10/15 18:12:24 阅读更多 →

最新新闻

【ESP32笔记】ESP中的分区表

【ESP32笔记】ESP中的分区表

本节目标:看懂分区表 CSV 的每一列,会给工程添加自定义分区,并用 esp_partition API 完成擦除、写入、读取一、本节要解决的问题 ESP32 的程序不是"整个 bin 一股脑烧到某个固定地址",而是把整块 flash 划分成多个分区&…

2026/10/15 19:04:55 阅读更多 →
亲和路由(inspect)

亲和路由(inspect)

Inspect 要解决的是“多轮对话在普通选路下被分到不同渠道”导致缓存失效和兼容性出错的问题;它通过回答“值不值得亲和、按什么粒度分组、用哪些稳定字段”三个问题,输出分类、触发原因和内容哈希;它被设计成无外部状态的纯分析器&#xff0…

2026/10/15 19:04:55 阅读更多 →
AI本体建模-------GB/T 48000.3—2026 标准详解

AI本体建模-------GB/T 48000.3—2026 标准详解

AI本体建模之 GB/T 48000.3—2026 标准详解 当大模型与 Agent 逐渐成为企业基础设施,真正的护城河正从「数据」转向「可被机器理解、推理、校验、共享的知识」。而 本体(Ontology) 正是这套知识体系的「模式层」与「契约层」。 2026 年 1 月&…

2026/10/15 19:04:55 阅读更多 →
线上 Redis 慢查询,不一定就是命令本身慢

线上 Redis 慢查询,不一定就是命令本身慢

上周值班的时候,监控突然弹出 Redis 慢查询告警,慢查询日志里全是 HGETALL,单条耗时经常冲到 80ms 以上。第一反应,又是经典问题,大 Hash 别用 HGETALL,这个知识点群里都说烂了。 一开始我也是这么处理的&a…

2026/10/15 19:04:55 阅读更多 →
有效三角形个数:排序+双指针经典算法详解

有效三角形个数:排序+双指针经典算法详解

做算法题的时候有一种快乐是:题目本身只有一行描述,但背后藏着一整类优化思路。有效三角形个数就是这样——表面上是几何问题,实际上是排序和双指针的经典练兵场,也是我算法题解系列第五篇想重点讲透的题目。题目很直白&#xff1…

2026/10/15 19:04:55 阅读更多 →
SDSegmentedControl避坑指南:iOS 6/7兼容处理、IB集成技巧与现代化改造建议

SDSegmentedControl避坑指南:iOS 6/7兼容处理、IB集成技巧与现代化改造建议

【免费下载链接】SDSegmentedControl A drop-in remplacement for UISegmentedControl that mimic iOS 6 AppStore tab controls 项目地址: https://gitcode.com/gh_mirrors/sd/SDSegmentedControl 点击查看 免费下载 SDSegmentedControl 是一个可以直接替换系统 U…

2026/10/15 19:02:09 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →