k9mail源码分析.docx
《k9mail源码分析.docx》由会员分享,可在线阅读,更多相关《k9mail源码分析.docx(31页珍藏版)》请在冰豆网上搜索。
k9mail源码分析
privatevoidfinishAutoSetup(){
Stringemail=mEmailView.getText().toString();
Stringpassword=mPasswordView.getText().toString();
String[]emailParts=splitEmail(email);
Stringuser=emailParts[0];
Stringdomain=emailParts[1];
URIincomingUri=null;
URIoutgoingUri=null;
try{
StringuserEnc=URLEncoder.encode(user,"UTF-8");
StringpasswordEnc=URLEncoder.encode(password,"UTF-8");
StringincomingUsername=mProvider.incomingUsernameTemplate;
incomingUsername=incomingUsername.replaceAll("\\$email",email);
incomingUsername=incomingUsername.replaceAll("\\$user",userEnc);
incomingUsername=incomingUsername.replaceAll("\\$domain",domain);
URIincomingUriTemplate=mProvider.incomingUriTemplate;
incomingUri=newURI(incomingUriTemplate.getScheme(),incomingUsername+":
"
+passwordEnc,incomingUriTemplate.getHost(),incomingUriTemplate.getPort(),null,
null,null);
StringoutgoingUsername=mProvider.outgoingUsernameTemplate;
URIoutgoingUriTemplate=mProvider.outgoingUriTemplate;
if(outgoingUsername!
=null){
outgoingUsername=outgoingUsername.replaceAll("\\$email",email);
outgoingUsername=outgoingUsername.replaceAll("\\$user",userEnc);
outgoingUsername=outgoingUsername.replaceAll("\\$domain",domain);
outgoingUri=newURI(outgoingUriTemplate.getScheme(),outgoingUsername+":
"
+passwordEnc,outgoingUriTemplate.getHost(),outgoingUriTemplate.getPort(),null,
null,null);
}else{
outgoingUri=newURI(outgoingUriTemplate.getScheme(),
null,outgoingUriTemplate.getHost(),outgoingUriTemplate.getPort(),null,
null,null);
}
if(mAccount==null){
mAccount=Preferences.getPreferences(this).newAccount();
}
mAccount.setName(getOwnerName());
mAccount.setEmail(email);
mAccount.setStoreUri(incomingUri.toString());
mAccount.setTransportUri(outgoingUri.toString());
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
mAccount.setArchiveFolderName(getString(R.string.special_mailbox_name_archive));
//Yahoo!
hasaspecialfolderforSpam,called"BulkMail".
if(incomingUriTemplate.getHost().toLowerCase().endsWith("")){
mAccount.setSpamFolderName("BulkMail");
}else{
mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam));
}
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
if(incomingUri.toString().startsWith("imap")){
mAccount.setDeletePolicy(Account.DELETE_POLICY_ON_DELETE);
}elseif(incomingUri.toString().startsWith("pop3")){
mAccount.setDeletePolicy(Account.DELETE_POLICY_NEVER);
}
//Checkincominghere.ThencheckoutgoinginonActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this,mAccount,CheckDirection.INCOMING);
}catch(UnsupportedEncodingExceptionenc){
//Thisreallyshouldn'thappensincetheencodingishardcodedtoUTF-8
Log.e(K9.LOG_TAG,"Couldn'turlencodeusernameorpassword.",enc);
}catch(URISyntaxExceptionuse){
/*
*IfthereissomeproblemwiththeURIwegiveupandgoonto
*manualsetup.
*/
onManualSetup();
}
}
MIME邮件的构成
MIME消息(邮件,对应k9mail的MimeMessage类),由消息头(对应k9mai的MimeHeader类)和消息体(body对应k9mail的Body接口)两大部分组成。
邮件头中不允许出现空行。
邮件头包含了发件人、收件人、主题、时间、MIME版本、邮件内容的类型等重要信息。
每条信息称为一个域,由域名后加“:
”和信息的内容构成,可以是一行,较长的也可以占用多行。
域的首行必须“顶头”写,即左边不能有空白字符(空格和制表符);续行则必须以空白字符打头,且第一个空白字符不是信息本身固有的,解码时要过滤掉。
邮件头信息中各个字段及其含义的说明如下表:
邮件体(body)指的是邮件的内容,它的类型由邮件头的“Content-Type”域指出,常见的简单类型有text/plain(纯文本)和text/html(超文本)。
multipart类型是MIME邮件的精髓,对应k9mail的MultiPart类。
邮件体被分为多个段或者多个部分,对应k9mail的Part接口,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。
常见的multipart类型有三种:
multipart/mixed,multipart/related和multipart/alternative。
从它们的名称,不难推知这些类型各自的含义和用处。
它们之间的层次关系如下图所示,从中可以看出,如果在邮件中要添加附件,必须定义multipart/mixed段;如果存在内嵌资源,至少要定义multipart/related段;如果纯文本与超文本共存,至少要定义multipart/alternative段。
为什么说是“至少”?
举个例子说,如果只有纯文本与超文本正文,那么在邮件头中将类型扩大化,定义为multipart/related,甚至multipart/mixed,都是允许的。
k-9-4.804\src\com\fsck\k9\mail
地址:
address.java
地址和通讯录名字之间的转换
身份认证:
Authentication.java
函数作用:
计算对CRAM-MD5认证机制的响应,给用户提供认证信息和服务器提供随机数。
packagecom.fsck.k9.mail;
importjava.security.MessageDigest;
importcom.fsck.k9.mail.filter.Base64;
importcom.fsck.k9.mail.filter.Hex;
publicclassAuthentication{
privatestaticfinalStringUS_ASCII="US-ASCII";
/**
*ComputestheresponseforCRAM-MD5authenticationmechanismgiventheusercredentialsand
*theserver-providednonce.
*
*@paramusernameTheusername.
*@parampasswordThepassword.
*@paramb64NonceThenonceasbase64-encodedstring.
*@returnTheCRAM-MD5response.
*
*@throwsAuthenticationFailedExceptionIfsomethingwentwrong.
*
*@seeAuthentication#computeCramMd5Bytes(String,String,byte[])
*/
publicstaticStringcomputeCramMd5(Stringusername,Stringpassword,Stringb64Nonce)
throwsAuthenticationFailedException{
try{
byte[]b64NonceBytes=b64Nonce.getBytes(US_ASCII);
byte[]b64CRAM=computeCramMd5Bytes(username,password,b64NonceBytes);
returnnewString(b64CRAM,US_ASCII);
}catch(AuthenticationFailedExceptione){
throwe;
}catch(Exceptione){
thrownewAuthenticationFailedException("Thisshouldn'thappen",e);
}
}
/**
*ComputestheresponseforCRAM-MD5authenticationmechanismgiventheusercredentialsand
*theserver-providednonce.
*
*@paramusernameTheusername.
*@parampasswordThepassword.
*@paramb64NonceThenonceasbase64-encodedbytearray.
*@returnTheCRAM-MD5responseasbytearray.
*
*@throwsAuthenticationFailedExceptionIfsomethingwentwrong.
*
*@see//tools.ietf.org/html/rfc2195">RFC2195
*/
publicstaticbyte[]computeCramMd5Bytes(Stringusername,Stringpassword,byte[]b64Nonce)
throwsAuthenticationFailedException{
try{
byte[]nonce=Base64.decodeBase64(b64Nonce);
byte[]secretBytes=password.getBytes(US_ASCII);
MessageDigestmd=MessageDigest.getInstance("MD5");
if(secretBytes.length>64){
secretBytes=md.digest(secretBytes);
}
byte[]ipad=newbyte[64];
byte[]opad=newbyte[64];
System.arraycopy(secretBytes,0,ipad,0,secretBytes.length);
System.arraycopy(secretBytes,0,opad,0,secretBytes.length);
for(inti=0;ifor(inti=0;imd.update(ipad);
byte[]firstPass=md.digest(nonce);
md.update(opad);
byte[]result=md.digest(firstPass);
StringplainCRAM=username+""+newString(Hex.encodeHex(result));
byte[]b64CRAM=Base64.encodeBase64(plainCRAM.getBytes(US_ASCII));
returnb64CRAM;
}catch(Exceptione){
thrownewAuthenticationFailedException("SomethingwentwrongduringCRAM-MD5computation",e);
}
}
}
k-9-.fsck.k9.mail.transport;
SmtpTransport.java
WebDavTransport.java
k-9-4.804\src\com\fsck\k9\mail\transport\imap
ImapSettings.java
com.fsck.k9:
Account.java
这是个实体类,也是android平台上MVC模式中的Model类,它除了封装Email账户的信息外,还被设计用于保存账户的各种设置,包括账户身份认证设置Identity、字体设置FontSizes、通知设置NotificationSetting和邮件收发地址、草稿箱、垃圾箱、总是密送到的账户、反垃圾用的文件夹、各种网络(3g、WiFi等)连接下是否启用压缩等,这些数据使用android平台的SharedPreferences保存,SharedPreferences是平台下除了SQLite外另外一种方便好用的数据持久化方式,应该是android平台上最简单的读写外部数据的方法,特别适用于保存客户端不同用户的个性化设置信息。
一个账户用一个UUID定义,可以通过mUuid属性的值来区分两个账户。
Accou类实现了接口BaseAccount,这个接口定义了获取、设置Email账户的及其描述的String类型的数据,还定义了获取账户UUID值的方法。
通过Account可以获得账户对应的LocalStore实例,然后可以进一步获得该账户在SQLite数据库里面保存的一切信息,包括邮件文件夹、邮件等,还可以获得远程邮件服务器的代理(RemoteStore,包括ImapStore、Pop3Store和WebDavStore,这些组件封装了对远程服务器的访问),并进一步获得其内部类(例如ImapStore的内部类ImapFolder),实现对远程服务器上相应对象的操作。
Account的类型怎么区分的?
通过Account类的属性mStoreUri,不同类型的Account的mStoreUri的值以不同的前缀开头,k9mail分别使用pop3、imap和webdav表示相应的三种Email账户类型,该属性的值的形式如下:
imap:
//PLAIN:
帐号:
密码@:
143/1%7C
这些数据变化后,k9mail会调用Account类的save方法通过SharedPreferences.Editor把这些数据保存到xml配置文件中。
该类存储了一个账号的所有信息。
Accountstoresallofthesettingsforasingleaccountdefinedbytheuser.ItisabletosaveanddeleteitselfgivenaPreferencestoworkwith.EachaccountisdefinedbyaUUID.
(1)Account的默认信息:
收发件箱、删除策略、网络类型、消息类型、键值、颜色、排序类、数据库ID等等
(2)protectedAccount(Contextcontext)
设置账户的基本信息:
(3)privateintpickColor(Contextcontext)
*PickaniceAndroidguidelinescolorifwehaven'tusedthemallyet.
(4)protectedAccount(Preferencespreferences,Stringuuid){
this.mUuid=uuid;
loadAccount(preferences);
}
(5)privatesynchronizedvoidloadAccount(Preferencespreferences)
*Loadstoredsettingsforthisaccount.
(6)protectedsynchronizedvoiddelete(Preferencespreferences)
删除一个账户
(7)publicstaticintfindNewAccountNumber(ListaccountNumbers)
为新账户建立一个AccountNumber
(8)publicstaticListgetExistingAccountNumbers(Preferencespreferences)
获得存在的所有帐户的AccountNumber列表
(9)publicstaticintgenerateAccountNumber(Preferencespreferences)
生成一个AccountNumber
(10)publicvoidmove(Preferencespreferences,booleanmoveUp)
移动账户的位置,上移或下移一位
(11)publicsynchronizedvoidsave(Preferencespreferences)
保存账户的配置信息。
当账户被创建时,我们会为其分配一个唯一的AccountNumber
(12)publicvoidresetVisibleLimits()
重置可以显示的账户的个数
(13)publicAccountStatsgetStats(Contextcontext)throwsMessagingException
获得帐户状态
(14)颜色相关的函数
publicsynchronizedvoidsetChipColor(intcolor)
publicsynchronizedvoidcacheChips()
publicColorChipgetCheckmarkChip(){
returnmCheckmarkChip;
}
publicsynchronizedintgetChipColor(){
returnmChipColor;
}
publicColorChipgenerateColorChip(booleanmessageRead,booleantoMe,booleanccMe,booleanfromMe,booleanmessageFlagged)
publicColorChipgenerateColorChip(){
returnnewColorChip(mChipColor,false,ColorChip.CIRCULAR);
}
(15)publicStringgetUuid(){
returnmUuid;
}
获得当前账户的Uuid
(16)publicUrigetContentUri(){
returnUri.parse("content:
//accounts/"+getUuid());
}
获得当前账户的ContentUri
(16)publicsynchronizedStringgetStoreUri(){
returnmStoreUri;
}
获得当前账户的StoreUri
(17)publicsynchronizedvoidsetStoreUri(StringstoreUri){
this.mStoreUri=storeUri;
}
设置当前账户的Store