Android 原生Email APP 代码解析(1)
练习Markdown的使用,总结一下以前的学习的内容,感谢老师提供的平台
最近听到一个段子就是说欧洲人在线上购买商品的时候还是用Email与商家沟通,中国的商家加入了IM(即时通信),感觉就很幸福了,这体验也太快了吧。网友品论就是Email 是一个听说过但是从来没有用过的应用。我们生活上用邮件的比较少,现在google也主推Gmail代替Email,但是不妨碍让我们了解一下Email。因为这个是原生的应用中使用网络协议最多的应用,可以让我们了解一下Google工程师是怎么封装网络通信。
Email 使用的网络协议介绍
原生Email的使用了POP3,IMAP,SMTP 和 Exchange 四个协议进行Email 发送和接受的
POP3协议
POP3是Post Office Protocol 3的简称,即邮局协议的第3个版本,协议允许电子邮件客户端下载服务器上的邮件,但是在客户端的操作(如移动邮件、标记已读等),不会反馈到服务器上,比如通过客户端收取了邮箱中的3封邮件并移动到其他文件夹,邮箱服务器上的这些邮件是没有同时被移动的 。
IMAP协议
IMAP全称是Internet Mail Access Protocol,即交互式邮件存取协议,与POP3类似,不同之处,开启了IMAP后,您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上。所以无论从浏览器登录邮箱或者客户端软件登录邮箱,看到的邮件以及状态都是一致的。
SMTP协议
SMTP 的全称是“Simple Mail Transfer Protocol”,即简单邮件传输协议。它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。SMTP 服务器就是遵循 SMTP 协议的发送邮件服务器。
Exchange协议
Exchange ActiveSync是一种微软 Exchange 同步协议,该协议可以访问服务器的邮件、日程、联系人,并且在脱机工作时仍可以继续访问这些信息。
原生Email的文件的路径
我看的是Android Marshmallow | 6.0.1_r16
Email 路径 根目录: packages/apps/Email
Exchange 路径 根目录: packages/apps/Exchange
原生Email 网络通信架构
核心接口IEmailService
packages/apps/Email/emailcommon/src/com/android/emailcommon/service/IEmailService.aidl
interface IEmailService {
void sendMail(long accountId);
int sync(long accountId, inout Bundle syncExtras);
int searchMessages(long accountId, in SearchParams params, long destMailboxId);
}
packages/apps/Email/provider_src/com/android/email/service/EmailServiceStub.java
/**
* EmailServiceStub is an abstract class representing an EmailService
*
* This class provides legacy support for a few methods that are common to both
* IMAP and POP3, including startSync, loadMore, loadAttachment, and sendMail
*/
public abstract class EmailServiceStub extends IEmailService.Stub implements IEmailService {
@Override
public Bundle validate(HostAuthCompat hostAuthCom) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
protected void requestSync(long mailboxId, boolean userRequest, int deltaMessageCount) {
final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId);
}
@Override
public void updateFolderList(final long accountId) throws RemoteException {
final Account account = Account.restoreAccountWithId(mContext, accountId);
}
@Override
public int searchMessages(final long accountId, final SearchParams params,
final long destMailboxId)
throws RemoteException {
// Not required
return EmailServiceStatus.SUCCESS;
}
@Override
public void pushModify(final long accountId) throws RemoteException {
LogUtils.e(Logging.LOG_TAG, "pushModify invalid for account type for %d", accountId);
}
@Override
public int sync(final long accountId, final Bundle syncExtras) {
return EmailServiceStatus.SUCCESS;
}
@Override
public void sendMail(final long accountId) throws RemoteException {
sendMailImpl(mContext, accountId);
}
}
packages/apps/Email/provider_src/com/android/email/service/Pop3Service.java
public class Pop3Service extends Service {
/**
* Create our EmailService implementation here.
*/
private final EmailServiceStub mBinder = new EmailServiceStub() {
@Override
public void loadAttachment(final IEmailServiceCallback callback, final long accountId,
final long attachmentId, final boolean background) throws RemoteException {
// We load attachments during a sync
requestSync(inboxId, true, 0);
}
};
@Override
public IBinder onBind(Intent intent) {
mBinder.init(this);
return mBinder;
}
}
packages/apps/Email/provider_src/com/android/email/service/ImapService.java
public class ImapService extends Service {
/**
* Create our EmailService implementation here.
*/
private final EmailServiceStub mBinder = new EmailServiceStub() {
@Override
public int searchMessages(long accountId, SearchParams searchParams, long destMailboxId) {
try {
return searchMailboxImpl(getApplicationContext(), accountId, searchParams,
destMailboxId);
} catch (MessagingException e) {
// Ignore
}
return 0;
}
};
@Override
public IBinder onBind(Intent intent) {
mBinder.init(this);
return mBinder;
}
}
IEmailService是Email的核心接口。定义了Email的基本功能。如发送邮件(sendmail),下载附件(loadAttachment),同步(sync)搜索邮件(searchMessages)等。EmailServiceStub实现了IEmailService接口。主要实现POP3和IMAP邮箱共同的一些方法。EmailServiceStub为抽象类,它有两个子类。这两个子类分别是POP3邮箱和IMAP邮箱对该接口的实现。这两个子类针对POP3和IMAP协议的不同重写了父类的某些方法。如POP3协议不支持单独的下载附件,在POP3的子类中重写了父类的loadAttachment。这两个子类分别为Pop3Service和ImapService的匿名内部类。Exchange协议的IEmailService实现位于EasService的匿名内部类中。
具体可以看一下这个图
smtp接收邮件
EmailServiceStub.java中sendMailImpl()方法定义了发送邮件主要是通过sender.sendMessage(messageId); 发送邮件的。
/packages/apps/Email/src/com/android/email/mail/transport/SmtpSender.java
/**
* This class handles all of the protocol-level aspects of sending messages via SMTP.
*/
public class SmtpSender extends Sender {
private final Context mContext;
private MailTransport mTransport;
private Account mAccount;
private String mUsername;
private String mPassword;
private boolean mUseOAuth;
/**
* Static named constructor.
*/
public static Sender newInstance(Account account, Context context) throws MessagingException {
return new SmtpSender(context, account);
}
/**
* Creates a new sender for the given account.
*/
public SmtpSender(Context context, Account account) {
mTransport = new MailTransport(context, "SMTP", sendAuth);
String[] userInfoParts = sendAuth.getLogin();
mUsername = userInfoParts[0];
mPassword = userInfoParts[1];
Credential cred = sendAuth.getCredential(context);
if (cred != null) {
mUseOAuth = true;
}
}
public void setTransport(MailTransport testTransport) {
mTransport = testTransport;
}
@Override
public void open() throws MessagingException {
try {
mTransport.open();
}
@Override
public void sendMessage(long messageId) throws MessagingException {
close();
open();
Message message = Message.restoreMessageWithId(mContext, messageId);
if (message == null) {
throw new MessagingException("Trying to send non-existent message id="
+ Long.toString(messageId));
}
Address from = Address.firstAddress(message.mFrom);
Address[] to = Address.fromHeader(message.mTo);
Address[] cc = Address.fromHeader(message.mCc);
Address[] bcc = Address.fromHeader(message.mBcc);
try {
executeSimpleCommand("MAIL FROM:" + "<" + from.getAddress() + ">");
for (Address address : to) {
executeSimpleCommand("RCPT TO:" + "<" + address.getAddress().trim() + ">");
}
for (Address address : cc) {
executeSimpleCommand("RCPT TO:" + "<" + address.getAddress().trim() + ">");
}
for (Address address : bcc) {
executeSimpleCommand("RCPT TO:" + "<" + address.getAddress().trim() + ">");
}
executeSimpleCommand("DATA");
// TODO byte stuffing
Rfc822Output.writeTo(mContext, message,
new EOLConvertingOutputStream(mTransport.getOutputStream()),
false /* do not use smart reply */,
false /* do not send BCC */,
null /* attachments are in the message itself */);
executeSimpleCommand("\r\n.");
} catch (IOException ioe) {
throw new MessagingException("Unable to send message", ioe);
}
}
@Override
public void close() {
mTransport.close();
}
private String executeSimpleCommand(String command) throws IOException, MessagingException {
return executeSensitiveCommand(command, null);
}
private String executeSensitiveCommand(String command, String sensitiveReplacement)
throws IOException, MessagingException {
if (command != null) {
mTransport.writeLine(command, sensitiveReplacement);
}
String line = mTransport.readLine(true);
}
}
从上面可以看出来主要是通过MailTransport.java进行封装的网络部分
Email/provider_src/com/android/email/mail/transport/MailTransport.java
public class MailTransport {
public void open() throws MessagingException, CertificateValidationException {
if (DebugUtils.DEBUG) {
LogUtils.d(Logging.LOG_TAG, "*** " + mDebugLabel + " open " +
getHost() + ":" + String.valueOf(getPort()));
}
try {
SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort());
if (canTrySslSecurity()) {
mSocket = SSLUtils.getSSLSocketFactory(
mContext, mHostAuth, null, canTrustAllCertificates()).createSocket();
} else {
mSocket = new Socket();
}
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
// After the socket connects to an SSL server, confirm that the hostname is as expected
if (canTrySslSecurity() && !canTrustAllCertificates()) {
verifyHostname(mSocket, getHost());
}
Analytics.getInstance().sendEvent("socket_certificates",
"open", Boolean.toString(canTrustAllCertificates()), 0);
if (mSocket instanceof SSLSocket) {
final SSLSocket sslSocket = (SSLSocket) mSocket;
if (sslSocket.getSession() != null) {
Analytics.getInstance().sendEvent("cipher_suite",
sslSocket.getSession().getProtocol(),
sslSocket.getSession().getCipherSuite(), 0);
}
}
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
} catch (SSLException e) {
if (DebugUtils.DEBUG) {
LogUtils.d(Logging.LOG_TAG, e.toString());
}
throw new CertificateValidationException(e.getMessage(), e);
} catch (IOException ioe) {
if (DebugUtils.DEBUG) {
LogUtils.d(Logging.LOG_TAG, ioe.toString());
}
throw new MessagingException(MessagingException.IOERROR, ioe.toString());
} catch (IllegalArgumentException iae) {
if (DebugUtils.DEBUG) {
LogUtils.d(Logging.LOG_TAG, iae.toString());
}
throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION, iae.toString());
}
}
public void reopenTls() throws MessagingException {
try {
mSocket = SSLUtils.getSSLSocketFactory(mContext, mHostAuth, null,
canTrustAllCertificates())
.createSocket(mSocket, getHost(), getPort(), true);
mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
Analytics.getInstance().sendEvent("socket_certificates",
"reopenTls", Boolean.toString(canTrustAllCertificates()), 0);
final SSLSocket sslSocket = (SSLSocket) mSocket;
if (sslSocket.getSession() != null) {
Analytics.getInstance().sendEvent("cipher_suite",
sslSocket.getSession().getProtocol(),
sslSocket.getSession().getCipherSuite(), 0);
}
} catch (SSLException e) {
if (DebugUtils.DEBUG) {
LogUtils.d(Logging.LOG_TAG, e.toString());
}
throw new CertificateValidationException(e.getMessage(), e);
} catch (IOException ioe) {
if (DebugUtils.DEBUG) {
LogUtils.d(Logging.LOG_TAG, ioe.toString());
}
throw new MessagingException(MessagingException.IOERROR, ioe.toString());
}
}
private static void verifyHostname(Socket socket, String hostname) throws IOException {
}
/**
* Close the connection. MUST NOT return any exceptions - must be "best effort" and safe.
*/
public void close() {
try {
mIn.close();
} catch (Exception e) {
// May fail if the connection is already closed.
}
try {
mOut.close();
} catch (Exception e) {
// May fail if the connection is already closed.
}
try {
mSocket.close();
} catch (Exception e) {
// May fail if the connection is already closed.
}
mIn = null;
mOut = null;
mSocket = null;
}
public InputStream getInputStream() {
return mIn;
}
public OutputStream getOutputStream() {
return mOut;
}
/**
* Writes a single line to the server using \r\n termination.
*/
public void writeLine(String s, String sensitiveReplacement) throws IOException {
if (DebugUtils.DEBUG) {
if (sensitiveReplacement != null && !Logging.DEBUG_SENSITIVE) {
LogUtils.d(Logging.LOG_TAG, ">>> " + sensitiveReplacement);
} else {
LogUtils.d(Logging.LOG_TAG, ">>> " + s);
}
}
OutputStream out = getOutputStream();
out.write(s.getBytes());
out.write('\r');
out.write('\n');
out.flush();
}
/**
* Reads a single line from the server, using either \r\n or \n as the delimiter. The
* delimiter char(s) are not included in the result.
*/
public String readLine(boolean loggable) throws IOException {
StringBuffer sb = new StringBuffer();
InputStream in = getInputStream();
int d;
while ((d = in.read()) != -1) {
if (((char)d) == '\r') {
continue;
} else if (((char)d) == '\n') {
break;
} else {
sb.append((char)d);
}
}
if (d == -1 && DebugUtils.DEBUG) {
LogUtils.d(Logging.LOG_TAG, "End of stream reached while trying to read line.");
}
String ret = sb.toString();
if (loggable && DebugUtils.DEBUG) {
LogUtils.d(Logging.LOG_TAG, "<<< " + ret);
}
return ret;
}
public InetAddress getLocalAddress() {
if (isOpen()) {
return mSocket.getLocalAddress();
} else {
return null;
}
}
}
上面的Socket通信是不是很熟悉了呢?这个就是Email app中基本的使用网络的部分,其他的未完待续。
参考网址
- Email5.0 代码结构 https://blog.csdn.net/fx1ts/article/details/46597251
- pop3 imap smtp 协议 http://help.163.com/09/1223/14/5R7P6CJ600753VB8.html http://help.163.com/09/1223/14/5R7P6CJ600753VB8.html
- Exchange 协议 http://help.163.com/10/0607/16/68JBVGP800753VB8.html