在Android项目中集成发送邮件功能

最近项目里有一个需求,要求在项目报错时可以发邮件到指定邮箱。这个功能不算难,但实现起来也有不少的坑。所以记录一下实现过程。

直接发送

首先明确一下直接发送的需求,这里要求的是直接在自己的App中点击发送,而不是跳转第三方的邮件App。

环境配置

这里发送邮件的实现方式是使用JavaMail for Andorid,需要用到android-mail和android-activation这两个包。
在gradle文件中加入以下配置。具体可参考https://javaee.github.io/javamail/#JavaMail_for_Android

当然, 你也可以下载Jar包集成到项目中,这里是下载地址:https://code.google.com/archive/p/javamail-android/downloads

Gradle文件配置:

1
2
3
4
dependencies {
compile 'com.sun.mail:android-mail:1.6.0'
compile 'com.sun.mail:android-activation:1.6.0'
}

这里需要注意的是,Release包下,还需要处理混淆文件,不然会导致Release包下功能失效。

在proguard-rules.pro文件中加入以下代码。

1
2
3
4
5
6
7
-keep class javax.mail.**{*;}
-keep class javax.mail.internet.**{*;}
-keep class com.sun.activation.registries.**{*;}
-keep class javax.activation.**{*;}
-keep class myjava.awt.datatransfer.**{*;}
-keep class org.apache.harmony.**{*;}
-keep class com.sun.mail.**{*;}

简单实现

环境配置好后,我们就可以直接在自己的App中发送邮件了。下面直接分享一下来自官网的Demo,https://javaee.github.io/javamail/docs/api/,亲测可行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Properties props = new Properties();
props.put("mail.smtp.host", "my-mail-server");
Session session = Session.getInstance(props, null);

try {
MimeMessage msg = new MimeMessage(session);
msg.setFrom("me@example.com");
msg.setRecipients(Message.RecipientType.TO,
"you@example.com");
msg.setSubject("JavaMail hello world example");
msg.setSentDate(new Date());
msg.setText("Hello, world!\n");
Transport.send(msg, "me@example.com", "my-password");
} catch (MessagingException mex) {
System.out.println("send failed, exception: " + mex);
}

当然了,这里的参数得改成你自己的,所以做下简单说明。

首先是第2行的props.put,这里可以理解为配置邮箱类别。这里key是不用改的,直接改它的value就可以。举个例子,如果是由126邮箱发出去的,就将value改为smtp.126.com;如果是outlook邮箱发出去的,就将value改为smtp-mail.outlook.com等等。这个值直接搜索某某邮箱的smtp设置就可以得到。

接着是设置Message。顾名思义,msg.setFrom是设置发件箱,msg.setRecipients第二个参数是收件箱,msg.setSubject是邮箱标题,msg.setSentDate是邮件日期,msg.setText是邮件内容。

最后是Transport.send发送,这里需要注意最后一个参数。对于大部分类别的邮箱,这个password不是邮箱的密码,而是邮箱的授权码,具体需要网页登陆自己的邮箱获取。

这样,最简单的邮件发送就实现了。由于是一个网络耗时操作,记得在子线程中调用,就可以成功发送邮件出去了。

项目封装

当然,实际项目中不可能像Demo一样简单,需要根据自己的需要做一些封装。

首先是封装邮件的消息体。如果要使用例如outlook这种不支持SSL,只支持TLS的邮箱,一定要加上prop.put(“mail.smtp.starttls.enable”, “true”)这句配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import java.util.Properties;

public class MailContext {
private String mailServerHost;
private String mailServerPort;
private String fromAddress;
private String toAddress;
private String userName;
private String password;
private boolean validate = false;
private String subject;
private String body;

public Properties getProperties() {
Properties prop = new Properties();
prop.put("mail.smtp.host", mailServerHost);
prop.put("mail.smtp.port", mailServerPort);
prop.put("mail.smtp.auth", validate ? "true" : "false");
prop.put("mail.smtp.starttls.enable", "true");
return prop;
}

public void setMailServerHost(String mailServerHost) {
this.mailServerHost = mailServerHost;
}

public void setMailServerPort(String mailServerPort) {
this.mailServerPort = mailServerPort;
}

public boolean isValidate() {
return validate;
}

public void setValidate(boolean validate) {
this.validate = validate;
}

public String getFromAddress() {
return fromAddress;
}

public void setFromAddress(String fromAddress) {
this.fromAddress = fromAddress;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getToAddress() {
return toAddress;
}

public void setToAddress(String toAddress) {
this.toAddress = toAddress;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getSubject() {
return subject;
}

public void setSubject(String subject) {
this.subject = subject;
}

public String getBody() {
return body;
}

public void setBody(String body) {
this.body = body;
}
}

接着是对权限的封装,也就是发件箱的账号和密码/授权码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;

public class MailAuthenticator extends Authenticator {
String userName;
String password;

public MailAuthenticator(String username, String password) {
userName = username;
password = password;
}

// This function will invoke in Authenticator
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
}

然后就是对发送方法的封装了,代码非常清晰简单,相信不用做过多解释就可以看懂。唯一要注意的就是,如果通过MimeMultipart来添加附件,会导致mailMessage.setText这个添加正文的方法无效,需要额外通过MimeBodyPart.setText来重新设置正文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.io.IOException;
import java.util.Date;
import java.util.Properties;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

public class MailUtils {
private static final String LOG_TAG = MailUtils.class.getName();

/**
* send mail
*
* @param mailContext mail context
* @param filePath If not have attach file, pass null argument
* @return send mail success or not
*/
public static boolean sendMail(MailContext mailContext, String filePath) {
MailAuthenticator authenticator = null;
Properties pro = mailContext.getProperties();
if (mailContext.isValidate()) {
// If need validate, create authenticator
Log.d(LOG_TAG, "create authenticator");
authenticator = new MailAuthenticator(mailContext.getUserName(), mailContext.getPassword());
}
Session sendMailSession = Session.getDefaultInstance(pro, authenticator);
try {
Message mailMessage = new MimeMessage(sendMailSession);
Address from = new InternetAddress(mailContext.getFromAddress());
mailMessage.setFrom(from);
Address to = new InternetAddress(mailContext.getToAddress());
mailMessage.setRecipient(Message.RecipientType.TO, to);
mailMessage.setSubject(mailContext.getSubject());
mailMessage.setSentDate(new Date());
String bodyText = mailContext.getBody();
mailMessage.setText(bodyText);

if (!TextUtils.isEmpty(filePath)) {
MimeMultipart multipart = new MimeMultipart();
// Set text part
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(bodyText);
multipart.addBodyPart(textPart);
try {
// Set attach part
MimeBodyPart attachPart = new MimeBodyPart();
attachPart.attachFile(filePath);
multipart.addBodyPart(attachPart);
} catch (IOException e) {
e.printStackTrace();
}
mailMessage.setContent(multipart);
}

// Send mail
Transport.send(mailMessage);
Log.d(LOG_TAG, "sendMail success");
return true;
} catch (MessagingException ex) {
ex.printStackTrace();
Log.d(LOG_TAG, "sendMail fail " + ex.toString());
return false;
}
}
}

最后就是代码的调用了。这里的FeedbackMessage类就是封装了邮件需要传的属性的一个Bean类,可以根据自己项目的需求自己来写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import com.google.gson.Gson;
import org.json.JSONException;
import org.json.JSONObject;

public class FeedbackManager {
private static final String LOG_TAG = FeedbackManager.class.getName();
private static final String FROM_ADDRESS = "miraclechen@126.com";
private static final String FROM_ADDRESS_PWD = "***********";
private static final String TO_ADDRESS = "1015800546@qq.com";

/**
* Send feedback message mail
*
*/
public static void sendFeedback() {
String mailContent = getFeedback();

MailContext mailContext = new MailContext();
mailContext.setMailServerHost("smtp.126.com");
mailContext.setMailServerPort("25");
mailContext.setValidate(true);
mailContext.setUserName(FROM_ADDRESS);
mailContext.setPassword(FROM_ADDRESS_PWD);
mailContext.setFromAddress(FROM_ADDRESS);
mailContext.setSubject("Feedback Title");
mailContext.setBody(mailContent);
mailContext.setToAddress(TO_ADDRESS);

MailUtils.sendMail(mailContext, null);
Log.d(LOG_TAG, "sendFeedback. mailContent: " + mailContent);
}

private static String getFeedback() {
FeedbackMessage feedbackMessage = new FeedbackMessage();
feedbackMessage.setUserName(BaseUtils.getUserName());
feedbackMessage.setDeviceModel(BaseUtils.getDeviceName());
feedbackMessage.setDeviceId(BaseUtils.getDeviceId());
feedbackMessage.setNetworkState(BaseUtils.isDeviceConnected() ? "Connected" : "DisConnected");
feedbackMessage.setNetworkType(BaseUtils.getNetworkType());
feedbackMessage.setErrorCode(BaseUtils.getErrorCode);
feedbackMessage.setErrorMsg(BaseUtils.getErrorMSg());

return new Gson().toJson(feedbackMessage);
}

第三方发送

在自己程序里发送邮件简单快捷,但由于之前是将发件箱定死了,每天如果发的邮件过多,发件箱可能会认为是在发垃圾邮件,会有将发件箱锁死,导致当天不能再发的风险。

所以,我们监测一下发件状态,在sendMail()方法里catch所有的exception。如果发送失败了,就跳转第三方的邮箱,并获取到发件箱,标题,正文等信息,由用户操作发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MailUtils {
private static void sendMailViaThirdPartyApp(MailContext mailContext, String filePath, Context context) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("message/rfc822");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{mailContext.getToAddress()});
intent.putExtra(Intent.EXTRA_SUBJECT, mailContext.getSubject());
intent.putExtra(Intent.EXTRA_TEXT, mailContext.getBody());
if (filePath != null) {
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filePath)));
}
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(intent);
}
}
}

总结

完毕收工!不管是在项目中直接集成邮件发送,还是跳转第三方邮件APP再发送,下次再实现起来都可以一气呵成了。

Chen wechat
欢迎扫描二维码,订阅我的博客公众号MiracleChen