Mms学习

来源:互联网 发布:c语言怎么导入函数库 编辑:程序博客网 时间:2024/05/18 02:36

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

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中的<application android:name="MmsApp" ,其中MmsApp就是com.android.mms.MmsApp类,在该类中分别处理了创建-onCreate、终止-onTerminate、配置改变-onConfigurationChanged三个生命周期回调方法,其中在onCreate方法进行了重要的系统初始化工作:

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

其中除了全局工具类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()方法,而该初始化方法是实现如下:

Java代码
  1. public static void init(final Context context) {  
  2.     new Thread(new Runnable() {  
  3.         public void run() {  
  4.             cacheAllThreads(context);  
  5.         }  
  6.     }).start();  
  7. }   

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/plaintext/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/amrSpeech: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呼入事件,具体代码如下:
Java代码
  1. private void handleSmsReceived(Intent intent) {  
  2.     SmsMessage[] msgs = Intents.getMessagesFromIntent(intent);  
  3.     Uri messageUri = insertMessage(this, msgs);   
  4.         //省略......  
  5.     if (messageUri != null) {  
  6.         MessagingNotification.updateNewMessageIndicator(thistrue);  
  7.     }  
  8. }   

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

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

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

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

接下来是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列表中,请看如下的关键代码:

Java代码
  1. sentIntents.add(PendingIntent.getBroadcast(mContext, 0,  
  2.   new Intent(SmsReceiverService.MESSAGE_SENT_ACTION,uri,mContext,SmsReceiver.class),  
  3.   0));  

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

  • 投递报告:发送消息方法的sendMultipartTextMessage专门有一个系统广播的参数用于报告投递情况的状态,关键代码如下:
Java代码
  1. deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0,  
  2.     new Intent(MessageStatusReceiver.MESSAGE_STATUS_RECEIVED_ACTION,  
  3.       uri,  
  4.       mContext,  
  5.       MessageStatusReceiver.class)  
  6. ,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()方法中:

Java代码
  1. // Phone.FEATURE_ENABLE_MMS is String "enableMMS"  
  2. ConnectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);  

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

Java代码
  1. //注册监听器 in TransactionService.onCreate()  
  2. mConnectivityListener = new NetworkConnectivityListener();  
  3. mConnectivityListener.registerHandler(mServiceHandler, EVENT_DATA_STATE_CHANGED);  
  4. mConnectivityListener.startListening(this);  

 

Java代码
  1. //解除注册监听器 in TransactionService.onDestroy()  
  2. mConnectivityListener.unregisterHandler(mServiceHandler);  
  3. mConnectivityListener.stopListening();  
  4. mConnectivityListener = null;  

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

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

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

…………;。。

 

 

MMS信息本地存储机制分析

:…………;。。

………;。。

………未完成.....