Android2.1消息应用(Messaging)源码学习笔记之二

阅读更多

Android2.1消息应用(Messaging)源码学习笔记之二

 

在上一篇学习笔记中,我从整理构成应用的主要组件开始,学习了相关的源代码,基本了解了Activity、Service、Rec eiver等组件的主要 职责和任务,接下来我将对重点功能的实现方法进行深入学习,了解它们的实现逻辑、涉及哪些API调用,以及是否与相关协议符合等各个方面的情况。

 

新消息呼入时系统的 响应过程及方法

Messaging应用对新消息呼入事件的 响应, 有3个配置选项可以由用户自己做出选择:

A.铃声 :用户可以选择已有 铃声/或者静音作为新消息呼入的提醒

该选项的配置是在MessagingPreferenceActivity.RingtonePreference中完成的,它通过ringtoneType属性将系统中的可用声音分为:①铃声(ringtone)、②通知(notification)、③警报(alarm)3种类型,指定类型后最终会通过RingtoneManager. ACTION_RINGTONE_PICKER选取到具体的 声音资源。
用户指定铃声被播放的关键在MessagingNotification工具类中,当有新消息时它的updateNewMessageIndicator()方法会被调用,该方法对 Notification. sound属性做了设置,使得铃音 最终得以 播放。当然在该方法中还包括了对状态栏图标、震动效果的处理逻辑。
而铃音被“适时
”播放的关键是:MM会在TransactionService的update方法(你是否还记得那个观察者模式,该方法是具体业务完成后的回调 )中针对NOTIFICATION_TRANSACTION和RETRIEVE_TRANSACTION两种业务调用 MessagingNotification 工具类 ,而SMS则是在SmsReceiverService的handleSmsReceived()方法中进行调用。

 

B.震动 :开启后有新消息呼入时会震动提醒。 响应过程 和铃声的 响应 机制完全一样,重点都在 MessagingNotification的 updateNewMessageIndicator()方法中

 

C.状态栏通知 :用户可以决定是否在状态栏上显示一个代表新消息的小图标, 响应过程 和铃声的 响应 机制基本一样 ,但它的触发不止是在信息呼入时,还会在信息发送失败、设备重新启动等情况下被调用,当然不同情况在状态栏会有不同的icon。

 

Messaging应用初始化以及简单的Cache系统

Messaging应用定义了自己的Application对象,详见 AndroidManifest.xml中的 三个生命周期回调方法,其中在onCreate方法进行了重要的系统初始化工作:

public void onCreate() {
    super.onCreate();
    PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
    MmsConfig.init(this);//读取系统配置参数
    ContactInfoCache.init(this);//启动联系人信息缓存系统
    Contact.init(this);//初始化联系人工具类
    DraftCache.init(this);//初始化草稿缓存系统
    Conversation.init(this);//初始化会话对象访问工具
    DownloadManager.init(this);//初始化下载管理器
    RateController.init(this);//初始化进度控制器
    DrmUtils.cleanupStorage(this);//Drm本地数据清理
    LayoutManager.init(this);//初始化部件管理器
    SmileyParser.init(this);//初始化SMIL解析器
}

其中除了全局工具类MmsConfig、LayoutManager、SmileyParser等对象的初始化之外,其余部分都是Cache系统的启动,本节将初步分析这些Cache系统的功能作用以及实现方法:

  • ContactInfoCache联系人信息缓存 :init方法其实是呼叫私有构建器,创建了该类的全局单实例对象。该类的职责是缓存联系人数据表中的查询结果,并提供相关方法访问联系人的常用信息,例如:显示名称等。该类中的getContactInfo()方法是非常有用的方法,你可以以给点的电话号码或E-mail地址作为参数查询联系人的常用信息,并且查询结果是被缓存下来的。与该类配合使用的还有Contact类,它同样内置了缓存系统,还有代表多个联系人的联系人列表对象ContactList,它也提供了很多方便使用的方法,用于组织联系人的基本信息,以及调用RecipientIdCache类的初始化方法,进一步向Cache系统丰富联系人的详细信息。
  • DraftCache草稿信息缓存 :init方法将触发构建器的调用,然后调用rebuildCache()方法,这会查询数据库,将其中所有草稿信息的会话ID全部缓存起来,供外部程序使用。

其它执行初始化的全局工具类,我会在后面相关环节谈及

 

会话Conversation的概念及其实现方法

会话Conversation是一种新的信息组织形式,不同于“ 传统 功能 ”以发件箱\收件箱\草稿箱,等 文件夹的 方式来 组织信息,会话会把上下文相关的“往<--->来”信息组织在一起,以方便用户查看管理。所谓上下文相关是指:若某条信息是对另一条信息的‘回复 ’,则认为它们是 上下文相关的。

Messaging应用的首页就是会话列表页面——ConversationList,它列出了用户所有往来 信息,及其未发出的草稿信息,这些内容都来自于ConversationListAdapter适配器。

与会话列表相关的查询操作封装在com.android.mms.data.Conversation类中,它是查询会话信息的接口,并在必要时创建新会话(一个全新信息还未有对应的上下文信息时 )。该类在应用启动的第一时刻就已经用一个后台线程 开始 加载会话列表了(加载到Cache中):

MmsApp.onCreate()方法调用了 Conversation.init()方法,而该初始化方法是实现如下:

public static void init(final Context context) {
	new Thread(new Runnable() {
		public void run() {
			cacheAllThreads(context);
		}
	}).start();
} 

cacheAllThreads()方法是关键,它将从content://mms-sms/conversations/位置查询数据,并在查询过程中标示了正在loading的状态——mLoadingThreads=true,它将查询出来的数据构建成Conversation对象放入Cache(或更新已存在于Cache中的对象),最后清理掉在Cache中存在,而未存在于查询结果中存在的 Conversation对象 (即清理无效的会话数据)

然而令我奇怪的是,listView中显示的数据并非来自Cache中,而始于异步查询工具 AsyncQueryHandler的子类 ThreadListQueryHandler 它以异步方式查询并得到会话列表,该查询开始于对 Conversation. startAsyncQuery()方法的调用。查询完成后, mQueryHandler中 调了mListAdapter.changeCursor(cursor) ,而此时才真正为 Adapter注入了有效的 Cursor对象

草稿信息也会在会话列表中出现,因为在 保存 草稿信息时 WorkingMessage .saveDraft()会最终呼叫到 Conversation. ensureThreadId方法,它会保证在数据表 (mmssms.db中的threads表) 中创建对应的会话记录,使其正确的显示在会话列表页面。

 

接收者编辑器RecipientsEditor的实现方法

一条信息可以发送给1到多个 目标 用户——它们被称为“接收者 ,以电话号码、E-Mail地址等形式存在。 “接收者 通常是从“联系人应用”中选择或由用户直接输入。

在Messaging应用中,用于输入 接收 者的控件有独立实现——RecipientsEditor,它继承自MultiAutoCompleteTextView, 我们知道 AutoCompleteTextView组件提供了输入时推荐可选项的功能,而 Multi...则意为着可输入多个项目(各项目间以逗号分隔),并分别推荐可选项。
推荐选项的数据源
来自RecipientsAdapter 适配器 ,它从ContactsProvider中读取MOBILE,WORK_MOBILE,MMS 三种类型 的电话号码作为推荐选项,在用户输入时自动匹配并推荐

说起接收者我们就不得不讨论Contact(代表接收者)、ContactList(代表接收者列表) 两个重要的类,他们提供了常用的组织联系人基本信息的方法,非常易于使用,同时它们也涉及到 Cache缓存系统

 

SMS <--->MMS 自动转换机制

在Messaging应用中, 编写普通短信SMS和编写彩信MMS的功能是整合在一起的,程序通过特定条件在两种 消息 类型之间 自动 转换,转换机制的入口时ComposeMessageActivity.toastConvertInfo()方法。初始创建的信息是SMS类型,当 出现 以下操作\ 或者条件成立时,会自动将消息从SMS转化为MMS:

  • setSubject:为消息设置主题通过为Subject编辑框mSubjectTextEditor设置addTextChangedListener而获得回调
  • setAttachment:为消息添加附件 (如:图片\视频) 通过选项菜单MENU_ADD_ATTACHMENT实现,由showAddAttachmentDialog弹出附件类型列表,并通过addAttachment方法予以响应处理(打开新Activity——SubActivity),然后通过onActivityResult回调方法 最终执行到 WorkingMessage.setAttachment 方法;
  • addressContainsEmail :接收地址中包含E-Mail地址 ,接收者输入框(mRecipientsEditor)有TextChangedListener——mRecipientsWatcher,它在接收者文本内容发生改变时调用mWorkingMessage.setHasEmail()方法以触发消息类型的自动转换机制;
  • LengthRequires:消息内容的长度超过SMS标准容量 ,用于输入消息文本内容的控件时EditText类型的mTextEditor,它有一个 TextChangedListener——mTextEditorWatcher,当消息文本内容被输入时会调用到updateCounter()方法,该方法计算了消息长度,并在符合条件时调用 mWorkingMessage.setLengthRequiresMms()方法来改变消息类型;

精确捕获具体操作以及条件状态发生变化的关键是——WorkingMessage类,该类代表了正在创建中的消息对象(我们知道 ComposeMessageActivity 类描绘了创建信息的UI,UI之后的数据对象便是WorkingMessage) 它记录着因UI操作而引起的各种状态变化,并将所有变化最终都通过 WorkingMessage.update()方法来予以执行实施。

另外一些状态变化被定义在WorkingMessage.MessageStatusListener接口中 (注意:当前ComposeMessageActivity是该接口的唯一实现者 包括消息类型发生改变时的回调,以及 WorkingMessage对象生命周期阶段的回调

  • onProtocolChanged:消息类型发生改变——即在SMS <----> MMS之间发生变化,该方法在ComposeMessageActivity中的实现是调用了toastConvertInfo(),它显示了Toast提示,通知用户消息类型发生了改变;
  • onAttachmentChanged:附件发生变化 (反映在 WorkingMessage.setAttachment方法 ) ——即在编辑MMS过程中添加/删除(在mAttachmentEditorHandler处理句柄中) 附件内容时回调;
  • onPreMessageSent: 消息 发送前,通过runOnUiThread方法在UI线程中调用了resetMessage(),这似乎是个现场清理逻辑,TODO:留待以后仔细学习吧。
  • onMessageSent: 消息 发送前,这并不代表消息发生成功,仅仅是指将消息投递的工作交给了底层网络而已。在 ComposeMessageActivity 中的实现会重新调用startMsgListQuery()方法,获得当前会话下的消息列表;
  • onMaxPendingMessagesReached:当发送队列满载时该方法被回调,在现有的实现中会将当前消息保存为草稿,并给用户一个恰当的提示;

以上就是消息类型的自动转换机制,以及消息状态变化监听器,这样的设计使得我们能够以更简单的方式添加针对 WorkingMessage 的新功能。

 

彩信MMS构成元素的类型以及编码格式

在3GPP的《MMS Media formats and codecs》规范中,详细定义了构成彩信的基本元素类型及其编码格式,其中包括:文本(Text)、语音(Speech)、声音(Audio)、合成声音(Synthetic audio)、静态图片(Still Image)、Bitmap图片(Bitmap graphics)、视频(Video)、矢量图(Vector graphics),

Messaging应用中,对以上各种元素及格式提供了广泛的支持,其中3个大类:图片、视频、音频 ,每种都可以选择现有文件或即时拍摄录制,对文件格式的支持详见下表:

 

项目 Messaging 协议 备注
文本 text/plain text/plain, 无格式文本,可UTF-8,ISO-8859-1
图片 image/jpeg 静态图片,jpeg,jfif
image/jpg
image/gif
image/png  Bitmap点阵图,GIF87a,GIF89a,PNG
image/vnd.wap.wbmp
视频 video/3gpp
video/3gpp2
video/h263  H.264(AVC),H.263 Profile 3 Level
video/mp4  MPEG-4
音频 audio/aac
audio/amr Speech:AMR,Enhanced aacPlus,Extended AMR-WB  
audio/imelody
audio/mid  Synthetic audio:MIDI
audio/midi
audio/mp3
audio/mpeg3
audio/mpeg
audio/mpg
audio/mp4
audio/x-mid
audio/x-midi
audio/x-mp3
audio/x-mpeg3
audio/x-mpeg
audio/x-mpg
audio/3gpp
application/ogg

从协议看并没有非常明确的支持要求,而Messaging应用对MMS构成元素的支持却相当广泛。

 

SMS业务实现机制分析

SMS业务包括:短信呼入、短信发出、SMS投递报告, 以及 本地存储等几个方面,以下我们将对应源代码,全面剖析各个业务的处理过程和实现方法:

  • 呼入 :有短信呼入时系统会发出android.provider.Telephony.SMS_RECEIVED广播,这最会调用到PrivilegedSmsReceiver的onReceiveWithPrivilege方法,在该方法内启动了处理呼入业务的SmsReceiverService类,该类持有一个后台工作线程并在ServiceHandler处理句柄中调用handleSmsReceived()方法来处理SMS呼入事件,具体代码如下:
private void handleSmsReceived(Intent intent) {
	SmsMessage[] msgs = Intents.getMessagesFromIntent(intent);
	Uri messageUri = insertMessage(this, msgs); 
        //省略......
	if (messageUri != null) {
	    MessagingNotification.updateNewMessageIndicator(this, true);
	}
} 

可以看出基本流程是:从intent中读取消息数据,调用insertMessage进行本地存储,然后再调用MessagingNotification的updateNewMessageIndicator()方法来通知用户。

仔细探究insertMessage()方法的实现,会发现它并非名副其实,它首先处理class zero类型的短信——直接显示,然后再判断是否 "replace short message" 若是则进行替换更新(update),否则才执行真正的insert将数据存储到数据库中。

  • 发出 :发送消息的触发点在“消息创建页面”的mSendButton按钮和选项菜单MENU_SEND上,它们调用了isPreparedForSending, confirmSendMessageIfNeeded两个方法,前者为发送前进行预检查,后者在条件成立的情况下发送消息。首先看预检查的基本逻辑:
private boolean isPreparedForSending() {
    int recipientCount = recipientCount();
    return recipientCount > 0 && recipientCount <= MmsConfig.getRecipientLimit() && (mWorkingMessage.hasAttachment() || mWorkingMessage.hasText());
}

逻辑 很简单,首先要有接收者并且接收者的个数不能超过限制,然后是必须要有文本内容或者必须要有附件内容(针对彩信而言)。

接下来是 confirmSendMessageIfNeeded方法了:它首先检查了“接收者输入框mRecipientsEditor是否可见 ”(当‘回复’信息时不可见,因为已有潜在接收者) ,不可见则直接调用sendMessage方法,否则会调用 mRecipientsEditor的相关方法验证接收者的有效性 :首先会检查是否有无效的接收者地址,如果有再检查是否所有接收者都是无效的,如果全是无效的则告知用户不能发送,否则弹出提示框——允许用户选择忽略无效地址,而向其它有效的接收者地址发送,这些检查都通过后,就来到了重要的 sendMessage方法,sendMessage方法 首先检查当前系统是否是在紧急呼叫模式(emergency callback mode ) ,如果是则显示提示界面并终止发送。否则直接调用WorkingMessage.send方法进行发送。

WorkingMessage.send方法首先调用prepareForSave()进行预处理——确认接收者列表、处理彩信相关的:syncTextToSlideshow、removeSubjectIfEmpty等等问题;然后取得所属会话对象mConversation,取得信息文本内容、再判断是否是SMS类型,若是 则直接在一个新线程运行sendSmsWorker()方法,紧接着就是调用接收者缓存系统的相关更新方法——RecipientIdCache.updateNumbers()。

sendSmsWorker是发送SMS类型短信的核心方法,它在开始和结束分别调用了消息发送前-onPreMessageSent和消息发送后-onMessageSent两个生命周期方法。它直接取得会话id、目标地址,然后将实际发送任务交给业务实体类SmsMessageSender。

SmsMessageSender类是负责发送SMS信息的关键业务类,它首先在构建器中取得消息文本、目标地址、时间戳、服务中心号码等重要信息,然后在sendMessage方法中执行实际的发送操作:核心工具类依然是SmsManager.getDefault(),然后由divideMessage方法将消息文本分割成若干符合规范长度的片段,再取得投递报告的设置状态,然后通过工具类将消息写入Sms.Outbox(短消息发件箱)位置,最后是针对每个目标接收者调用sendMultipartTextMessage方法完成消息发送,而对于消息发送结果进行响应的关键在sentIntent列表中,请看如下的关键代码:

sentIntents.add(PendingIntent.getBroadcast(mContext, 0,
  new Intent(SmsReceiverService.MESSAGE_SENT_ACTION,uri,mContext,SmsReceiver.class),
  0));

消息发送完成后会发出Intent.action=SmsReceiverService.MESSAGE_SENT_ACTION 的广播信息,并强制接收者为SmsReceiver,而该接收者最终会委托给SmsReceiverService类的handleSmsSent方法进行处理。

  • 投递报告 :发送消息方法的sendMultipartTextMessage专门有一个系统广播的参数用于 报告 投递情况的状态,关键代码如下:
deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0,
	new Intent(MessageStatusReceiver.MESSAGE_STATUS_RECEIVED_ACTION,
	  uri,
	  mContext,
	  MessageStatusReceiver.class)
,0));

关键在于Intent.action= MessageStatusReceiver.MESSAGE_STATUS_RECEIVED_ACTION 的广播信息,并且显式指定了处理该广播的目标类MessageStatusReceiver,该类首先取得回应广播(Intent)中的PDU,然后更新了数据库中的消息状态

  • 本地存储 :将SMS数据 存储到本地数据库中的几个关键在SmsReceiverService类的storeMessage、replaceMessage方法中,下面我们就来详细分析这两个方法的实现: …………待续 …………待续 …… …………待续 …… 。。。

 

MMS业务实现机制分析

彩信收发时网络连接的创建过程

在Android系统所在的移动设备上,可以存在多个潜在的网络连接通道,例如:GPRS、Wi-Fi、UMTS等 。对于移动数据网络GPRS而言还有APN的概念。APN(Access Point Name) 中文称为 “接入点”,它是手机建立无线数据网络连接的必要参数,事实上它就像我们所熟知的代理服务器 一样 ,作为终端访问网络的 入口 代理而存在 。以中国移动的GPRS为例,常用的APN有两种:CMWAP、CMNET。一般情况下各个APN都可能成功发起网络连接,但根据移动网络的运营情况,不同的数据业务在不同的接入点上可能有不同的计费方式。

Android系统中建立网络连接的方法是调用ConnectivityManager工具类的 startUsingNetworkFeature()方法,方法执行结果——即连接是否成功建立的状态值,由命名为 ConnectivityManager.CONNECTIVITY_ACTION常量的Intent广播消息来通知。

在Messaging应用中,用于建立( 收发彩信的 )网络连接的代码在TransactionService服务类的beginMmsConnectivity()方法中:

// Phone.FEATURE_ENABLE_MMS is String "enableMMS"
ConnectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);

而用于监听网络连接是否建立成功 监听器,分别在 TransactionService.onCreate() 和TransactionService.onDestroy()方法中 进行注册与解除

//注册监听器 in TransactionService.onCreate()
mConnectivityListener = new NetworkConnectivityListener();
mConnectivityListener.registerHandler(mServiceHandler, EVENT_DATA_STATE_CHANGED);
mConnectivityListener.startListening(this);

 

//解除注册监听器 in TransactionService.onDestroy()
mConnectivityListener.unregisterHandler(mServiceHandler);
mConnectivityListener.stopListening();
mConnectivityListener = null;

在当前的SDK中,NetworkConnectivityListener类被@hide了, 无法直接访问,阅读其源代码可以知道它创建了一个内置的、用于接收特定广播消息的ConnectivityBroadcastReceiver类,并将网 络连接的创建结果封装到NetworkInfo、Reason等属性中,并且它 以向mServiceHandler发送特定Message来达到对业务方法的回调效果

原 来在阅读TransactionService类的注释时了解到,MM可以透过mobile data network 和 wi-fi network两种方式来收发。但阅读具体源码后发现并不是这样,收发彩信的网络连接只能是 ConnectivityManager.TYPE_MOBILE类型。

网络连接建立广播最终会转化为mServiceHandler收到的TransactionService.EVENT_DATA_STATE_CHANGED消息, mServiceHandler首先验证连接有效性,再通过sendMessageDelayed()方法建立时间间隔为30秒的计时器,轮询调用 beginMmsConnectivity()方法,以保持连接的持续存在。TransactionService把所有信息收发业务置于“任务队列(mProcessing & mPending)”中,并依次进行处理,因此在所有任务处理完成之前保持连接的持续存在是很重要的。

…………;。。

 

 

MMS信息本地存储机制分析

:…………;。。

………;。。

………未完成.....

你可能感兴趣的:(Android2.1消息应用(Messaging)源码学习笔记之二)