这里的Mms名称并不准确,因为它是一个应用程序的名字,这个应用程序是Android上面的信息应用程序,它即能处理短信SMS(Short Messaging Service),也能处理彩信(Multimedia MessagingService)。它是除电话程序外,另一个非常重要的手机系统核心应用程序,因为对于手机来讲,最重要的二个功能就是电话功能和信息功能。在功能手机时代,是这样,到了智能手机时代,仍然是这样,如果一个手机不能打电话或者不能收发信息,那么这个东西就不能叫做手机(砖头?)。
为了不引起混淆,下面将Mms作为信息应用程序的简称,SMS则是短消息,MMS代表多媒体彩信,如不特殊说明信息将包括SMS和MMS。
与功能手机时代的信息应用一样,Android的Mms也具有通用的信息程序所具有的功能,比如创建信息,收发信息,转发信息,以及对于信息的管理,还有就是对于信息的一些配置。
与功能手机不同的是,信息不再是以传统的方件夹方式来组织和管理(收信箱,发信箱,草稿箱,已发信息,短信,彩信等),信息以某种方式排序(时间,主题),把所有的信息罗列在相应的文件夹中(收到的信息在收信箱中,发出去的信息在已发信息中,草稿在草稿箱中,正在发送的信息在发信箱中),信息的组织方式有所改变。智能手机引入了对话(Thread)的概念,也即把二个联系人之间的信息交互看成一个系列对话,二人之间的所有信息都列在对话之中。每个对话中,又以时间为序来管理具体的信息,以不同的着色来区分发出去的信息和收到的信息,在信息的旁边还可以显示联系人的信息,如头像,点击头像还可以有其他的快捷操作(比如拨号)。Mms管理一系列对话,从而没有了传统的各种文件夹。所以,对于Mms来讲核心概念是对话,而非信息或文件夹。
同样,智能手机也不再区分短信和彩信,而是把它们统一看作一个对话中的信息,当然,彩信和短信还是有区别的,主要在发送和显示这块,在信息管理理上不再区分彩信和短信(传统手机都会专门的短信文件夹,彩信文件夹),也就是不会有彩信文件夹,短信文件夹,而是统一的以对话形式来管理它们,而在对话中,它们都是信息,只是里面内容不同而已。
Mms中不再有收信箱,发信箱和草稿箱,而是以统一的对话列表来代替。一个对话中既包括收到的信息也包括发出去的信息,另外以状态信息方式来标识每个对话的状态,比如有无草稿,是否发送成功等。
如前面所说,对话是新一代信息程序的核心概念。对话是发生在二个联系人之间,或是一个联系与多个联系人之间的信息交互,就好像IM(MSN, QQ)的聊天窗口一样,二人的信息都显示在窗口之中,但这些信息只发生在二人之间。以对话的方式来组织信息更加符合人们的使用习惯,也更加方便的查询交往记录。
因为对话是发生在二个联系人之间的,所以对话与联系人的关系是十分密切的。因为,一对话中的信息都属于这二个联系人之间的。所以,通常来讲对话与联系人是一一对应的关系,除非发给多个联系人的信息。或者,对话是依赖于联系人的,所以对话是不能够被显示的创建的,只能显示的创建信息。当创建一个信息时,如果联系人还没有相应的对话,那么会自动创建一个对话;如果已经有了的话,就进入相应的对话。
在对话列表可以管理对话,进行操作,比如进入对话,删除对话,查看联系人等。进入对话后,可以对信息进行操作,比如编辑,删除,转发等。
一些关键的概念
Thread对话---是指用户与某个联系人或某几个联系人之间的一系列信息交互。在Mms中,用Thread Id来标识和管理对话,Thread Id也即在数据库表threads中的_id。
可能用Conversation是更易于理解。但是Thread本身就有对话的意思,某些论坛中的一个帖子在英语里就叫Thread。Thread的词典释义是:”因特网上关于一个题目一连串的信息 (计算机用语)”,所以这里用Thread,也是比较恰当的。
Conversation--是用来管理Thread对话的,Conversation是一个Thread对话的抽象出来数据结构,它能够,从数据库中查询,删除一个对话中的消息等,每一个Conversation有一个唯一的Thread Id。但是它也负责一些所有对话的管理,比如查询所有的对话,删除所有的对话等(这个应该是设计上面的问题)。
事实上,Conversation更多时候是充当前前对话的角色,比如在新新建信息时,编辑信息时,或是查看某个对话时,都会有一个Conversation对象存在在,以代表当前信息所处的对话。它是一个近似单键,都是通过Conversation的静态方法来获得Conversation对象,有一些其他的方法也是静态的。
ConversationList--负责显示和编辑所有的对话,以列表形式显示所有的Thread,每一项代表一个Thread,通常也会显示这个Thread的状态,如有无草稿,信息发送/接收是否成功等。
Message--消息,泛指短信SMS和彩信MMS。因为不再区分短信和彩信,在对话列表,草稿管理和信息列表中它们都是一样的,都是信息。Message的数据结构是MessageItem,它是一个纯数据结构,里面存储着关于一个信息的所有数据,还有MessageListitem,它是一个View,专门用于在消息列表中显示一个信息,里面的数据都是从MessageItem获取。它们统一都被ComposeMessageActivity,MessageListAdapter和MessageListView来管理。
WorkingMessage--当前消息,它是专门用于代表当前正在创建和编辑的信息的数据结构无论是短信还是彩信,在创建和编辑的时候都放在一个WorkingMessage对象里面。这个对象也负责信息的发送,存储和存储为草稿。
Slideshow--在Mms应用里面,彩信是以Slideshow幻灯片的形式来展示的。一个彩信可以有多张幻灯片,每张幻灯片上面可以有图片,文字,音频和视频,可以设置每张幻灯片的浏览时长,布局等,这里的幻灯片与Office中的PowerPoint有几分类似。幻灯片的数目限制是以彩信允许的附件大小为上限,这个也与每张幻灯片上面的媒体大小有关。可以这样讲MMS就是以幻灯片形式存在的,创建的时候是一张幻灯片一张幻灯片的编辑,收到的彩信或编辑完后,就可以一张张的放映浏览幻灯片。
需要注意的是以幻灯片方式显示彩信仅是应用程序层的处理方式,不同的信息应用程序会以不同的方式处理彩信,实际的彩信的数据是以标准的Pdu方式进行发送和接收,是应用程序在发送前把幻灯片转化成为Pdu,并在接收后把Pdu转化成为自己可识别的幻灯片。
Recipient接受人,这里是指信息的接收者,要么是一个陌生的电话号码,要么是一个陌生的电子邮件地址(彩信时),要么就是手机联系人数据库中的联系人。彩信和短信对接收人的数量都有限制,这个也是在Mms的Settings时面可以更改的。每一条信息要想发送成功,必须保证接收人是一个合法的联系人,合法也是不同的手机有不同的定义,但通常来讲,要么与联系人数据库中的某个联系人匹配,要么是一个电话号码,要么是一个电子邮件地址,其他情况则视为不合法,对于有不合法接收人的信息,不会进行发送。管理联系人的数据结构是Contact和ContactList,其中ContactList是一个以Contact为元素的ArrayList。Contact不但存储有联系人的一些信息,如名字,电话号码等,它还能与联系人数据库进行同步,也就是它能保证它是一个合法的联络人,并在数据库中存在。在信息发送前会先进行一次联系人同步,以保证已有的联系人是正确的。
因为Mms是手机核心程序,它与电话程序一样要通过手机的Modem来实现无线通讯,也就是说它是依赖于SIM的。所以,如果手机中没有SIM那么是无法正常使用Mms的,不能接收和发送信息。
Mms源码结构从实现的角度来看,它分为GUI展示层,发送/接收,彩信解析,彩信附件,信息数据等,这些分类对应着源码中的各种包。
源码导航 Mms的源码的位置在于Android/packages/apps/Mms
其中Mms/src/com/ Android/mms里面都是Mms相关的代码,而Mms/src/org/w3c/dom里面是一个类库,主要用于彩信格式的解析和显示。这里主要讲一下Mms/src/com/android/mms下面的一些包和类的主要用途。
ui---GUI展示层,用于展示对话列表,消息列表,消息编辑页,彩信附件编辑,彩信展示,播放幻灯片。负责直接与用户交互。
- ConversationListAdapter.java---对话列表的Adapter用于给显示层ConversationList绑定数据。
- ConversationListItemData.java---代表对话列表中的每一项的数据结构,里面含有要在对话列表中展示的信息。
- ConversationList.java------这是对话列表的显示窗口Activity,它是一个ListActivity,这几个类都是对话列表的相关类,用于显示,编辑和管理所有的对话。
- ComposeMessageActivity.java----这个是核心的窗口Activity,编辑信息,显示一条对话Thread中的所有往来信息。MessageListView会加在其上面,另外,AttachmentEditor也会加在其上面。这个Activity也负责响应外部应用程序,发送SENDTO或SEND等请求Intent,比如外部应用想要发送信息,等就由这个Activity来响应。
- MessageItem.java---代表一个信息的抽象数据,它包含了信息相关的所有内容,比如信息的主题,消息内容,来信地址,附件内容等等。它的所有数据都是公共的内部成员,都可以直接访问。
- MessageListAdapter.java---用于给消息列表显示层(由ComposeMessageActivity创建,绑定到MessageListView上)绑定数据。
- MessageListView.java---用于显示消息列表,继承自ListView,其生命周期由ComposeMessageActivity来控制,显示与否也由它来控制。
- MessageListItem.java---是一个布局,用于显示和控制消息列表中的每一个消息的显示。
- AttachmentTypeSelectorAdapter.java---用于添加附件件时的一个支持的附件列表,它就是一个菜单。
- AttachmentEditor.java---用于在编辑MMS彩信信息时,显示已添加的附件,它的生命周期由ComposeMessageActivity来控制,显示与否也是由ComposeMessageActivity来控制,当有彩信附件时,它就会显示,否则就被Hide。它是一个布局管理器,管理着下面四个布局,根据附件的类型动态的显示下面四个View中的某一个。
- AudioAttachmentView.java---在编辑信息器中用于显示音频附件,它是继承自线性布局。并不在代码中直接使用,而是在布局文件中来当成布局管理器使用。
- ImageAttachmentView.java---在编辑信息器中用于显示图片附件,它是继承自线性布局。并不在代码中直接使用,而是在布局文件中来当成布局管理器使用。
- SlideshowAttachmentView.java---在编辑信息器中用于显示幻灯片附件,它是继承自线性布局。并不在代码中直接使用,而是在布局文件中来当成布局管理器使用。
- VideoAttachmentView.java---在编辑信息器中用于显示视频附件,它是继承自线性布局。并不在代码中直接使用,而是在布局文件中来当成布局管理器使用。
- SlideshowActivity.java—用来全屏播放幻灯片,也即幻灯片的展示,因为彩信的创建和播放都是以幻灯片的方式进行的,也即一张一张的,每张上面可以文字,图片,视频和音频,每一张有浏览时长。
- SlideshowEditActivity.java---以列表方式管理幻灯片,也即是把所有的幻灯片用列表显示出来,用户可添加一页幻灯片,也可以点击进入编辑某页幻灯片,用于创建和编辑幻灯片。
- SlideshowEditor.java---用于编辑某页幻灯片,比如添加元素,删除元素和替换元素,这里的元素可以是图片,视频,音频和文字。也可以用于编辑整页幻灯片,比如删除某页幻灯片,调整这页幻灯片在所有幻灯片中的位置等。它是一个具体操作幻灯片的封装,SlideEditorActivity创建它并使用它来完成纪灯片的编辑。
- SlideshowPresenter.java---用于展示所有的幻灯片,也就是播放所有的幻灯片。由SlideshowActivity来创建和使用。
- SlideViewInterface.java---定义了一些用于显示一页幻灯片中的内容的接口,如设置图像,设置视频,设置音频,播放视频,播放音频,暂停,随机定位等等。附件显示的View:AudioAttachmentView,ImageAttachmentView,SlideshowAttachmentView和VideoAttachmentView均实现了此接口,这样AttachmentEditor就可以用统一的接口来控制内容的播放,而不用关心具体的内容是什么。
- SlideEditorActivity.java---用于编辑某页幻灯片,比如添加音频,添加视频,添加图像,添加文字等。它只是提供用户界面,让用户来操作各种按扭以达到添加元素,替换元素或是删除元素。而对具体的幻灯片的操作是通过SlideshowEditor来完成的,它主要负责与用户交互。
- SlideListItemView.java--- SlideshowEditActivity中列表的每一项的布局管理,继承自LinearLayout。
- MmsThumbnailPresenter.java---用于在消息列表中,显示彩信的缩略图,因为彩信的内容不固定,可能是图片,可能是音频,可能是视频也可能是幻灯片,所以用这个类来处理并显示彩信的缩略图。
- MessagingPreferenceActivity.java---Mms的配置信息编辑器,用来编辑和更改配置信息,继承息PreferenceActivity。它负责与用户交互,显示和更改配置。在Mms启动时,MmsConfig会从SharedPreference中读出配置信息,在运行时其他的类的配置信息都是从MmsConfig中获取的,MmsConfig提供了很多Get方法以获取配置信息。
- Presenter.java---用来展示附件的一个抽象类。
- PresenterFactory.java---工厂方法。
- RecipientsAdapter.java
- RecipientsEditor.java---用于显示信息编辑页面上面的收信人的编辑框,它可以有自动补全的功能,补全的数据由RecipientsAdapter来提供。
- ViewInterface.java---代表一个View的基类,用于Slideshow显示内容或是取缩略图。可以取View的长宽高等。
- BasicSlideEditorView.java---编辑某一页幻灯片时所用的布局,也就是在SlideEditorActivity.java中使用。
- EditSlideDurationActivity.java---顾名思义,用于编辑某一页幻灯片的浏览时长。
- ManageSimMessages.java---这个是在设置中使用的,用来管理SIM里的消息。在设置中有一项是管理SIM卡上面的消息。在Mms的设置Settings中有一个选项可以设置是把信息存储在SIM卡,还是存储在手机里。在收信时SmsReceiverService会查看这个设置然后把收到的信息写到相应的地址。ManageSimMessages也是以列表方式显示SIM里面的信息,提供了二个菜单:把信息存入手机和删除。
- NumberPickerButton.java---用于显示选择数字的按扭,在配置里面用。
- NumberPickerDialog.java---用于显示选择数字的对话框,在配置里面用。
- NumberPicker.java---用于在配置的时候选择数字。这几个NumerPicker主要是用于Settings中的。
- DeliveryReportActivity.java---信息发送情况报告。以列表的方式来显示
- DeliveryReportAdapter.java---相应的Adapter
- DeliveryReportItem.java---相应的数据,每一项的数据
- DeliveryReportListItem.java---相应每一项的布局。
- WorkingMessage.java---用来管理当前正在编辑的消息,它从创建,草稿到发送完成后一直存在,只要打开了编辑信息的页面就会创建一个WorkingMessage,直到退出编辑页面。
- Conversation.java---用来管理对话Threads,通常用来管理当前的对话,也就是进入的对话和正在进行操作的对话,它也用来管理对话列表,比如查询对话列表。
- Contact.java---用来代表一个联系人的信息,和管理联系人,加载联系人信息,其中还有相应的Cache。因为一个联系人的数据是比较多的包含名字,名,姓,各种电话号码,各种地址等等。因为Mms中直接使用Contact来作为联系人,所有信息都是直接从其中获取。另外,由于信息交互中也会涉及到联系人,因为收发信时可以直接使用一串电话号码,这时就需要有如添加联系人的功能。Contact中有很多异步的操作,比如加载联系人信息的时候或者更新Cache的时候都需要异步操作以不阻塞调用者。
- ContactList.java---是一个Contact的List列表它继承自ArrayList<Contact>。用来管理一个Contact列表,或管理多个Contact。因为每个信息可以发送给多个联系人,这时就需要用到ContactList来管理这些收信人。也提供了一些方便存储和传递Contact的方法,比如把多个Contact转成String,或者转成String数组等。
- RecipientIdCache.java---用于保存所用到的Contact的Id和地址(电话)。每次WorkingMessage会更新这个Cache,然后ContactList会优先从这个Cache中查询联络人。
drm---用于处理DRM的媒体文件的工具包
layout---为了满足特殊需要而改写的布局元素
model---这里面定义了彩信支持的附件数据结构和附件的组织方式。彩信可包含的内容有图片,视频,音频和文字。这些内容可以单独存在,也可以组合在一起。如果组合在一起就变成了幻灯片。用户可以用幻灯片的方式来创建含有多个媒体的附件,图文并茂的展示。每张幻灯片上面可以加视频,音频,图片和文字,但通常一张幻灯片上面只允许加一个图片或视频,文字是都可以添加的,音频在没有视频的情况下只可以添加的。播放的时候可以设置每张幻灯片的播放时长,以及文字的滚动速度等等。
- CarrierContentRestriction.java---是具体的彩信附件检查站,对于不支持的附件,或者附件大小超出限制,或者图像分辨率不对,或者图像超出尺寸,会抛出异常:UnsupportedContentTypeException,ResolutionException,ExceedMessageSizeException,ContentRestrictionException。
- ContentRestriction.java是用于检查附件的接口,外部直接使用这相接口,而具体实现是CarrierContentRestriction
- ContentRestrictionFactory.java是创建附件检查的工厂方法。外部通过这个工厂来创建一个ContentRestriction对象,然后使用其中定义的检查方法来进行附件内部检查。
- SmilHelper.java用于解析和处理附件中的Smil的工具类。
- IModelChangedObserver.java接口,用于监听附件内容有变化。
- Model.java---彩信附件的数据组织方式和管理方式是每一个附件都是一个Model的子类,它不但用于管理附件的具体数据,比如Uri,大小,文件名,位置等,也可以用于在GUI显示附件和查看附件。
- LayoutModel.java---继承自Model用于管理可视的附件的布局的类。它用来管理RegionModel等的基本元素。它就好比ViewGroup或LinearLayout,RelativeLayout等一些布局管理器,用来组织并管理布局基本元素也就是RegionModel的子类ImageModel,TextModel和VideoModel。
- RegionModel.java---继承自Model用于管理可视附件和布局,比如图像,视频和文字。特别是在显示可视附件的时候,用于控制可视附件在屏幕中的位置。一个RegionModel代表着一张幻灯片上的一块区域,它是幻灯片上的布局基本元素。好比UI元素中的View,但多在使用时都是使用它的子类,也就是ImageModel,TextModel和VideoModel。
- RegionMediaModel.java---继承自MediaModel,是用于多媒体附件中的可视部分的布局控制,主要用在附件的显示和播放幻灯片时的控制。它的子类是ImageModel,TextModel和VideoModel。
- MediaModel.java---继承自Model,代表媒体的数据结构,管理具体的附件数据,同时也用于管理附件的显示控制,比如图像的显示,音频和视频的播放控制等。
- MediaModelFactory.java---用于从一个Pdu附件中解析出来MediaModel,也就是把Pdu转化为Mms内部的附件数据。
- ImageModel.java—继承自RegionMediaModel用于管理图像附件和控制图像附件的显示。
- VideoModel.java---继承自RegionMediaModel用于管理视频附件和控制视频附件的播放。
- AudioModel.java----继承自MediaModel用于管理音频附件和控制音频附件的播放
- SlideModel.java----继承自Model用于管理一组附件,这些附件同一次显示给用户。就好像幻灯片的一片一样,每一个SlideModel里面有一个可以存储Model的列表,可以包含文字,音频,图像或视频,其上面的附件同时显示出来。
- SlideshowModel.java---继承自Model,用于管理一个彩信中的所有附件。其内含有一个存储SlideModel的列表,用于保存和控制一条彩信中的所有附件。另外它也负责显示这些附件,把一个个SlideModel组织起来,播放。它也负责着把这些Mms形式的附件(各种Model)转化为Android的附件Pdu,和从Pdu提出各自Model,因为Slideshow是应用程序层的彩信处理方式,而能发送和接收的彩信数据是Pdu。
- TextModel.java---继承自RegionMediaModel用于管理文字附件和控制文字附件的显示,比如按时间来滚动
- AddressUtils.java---关于地址的工具类,目前只有一个getFrom()方法,用于获取发信人地址。
- DraftCache.java---用于标识哪些对话Thread有Draft,哪些没有,也就是用于管理和查询对话的草稿状态,有草稿还是没有。它里面维护了一个HashSet,里面包含了所有含有草稿的Thread Id。它里面也有一个HashSet用于存储OnDraftChangedListener,即当Thread的Draft状态有变化时,DraftCache会调用相应的Listener以告知相应模块,这个对话的草稿状态有所变化。可以通过DraftCache.setDraftState(threadId, state)来设置某个对话的草稿状态; 可以通过DraftCache.hasDraft(threadId)来查询某个对话是否含有草稿。
- Recycler.java---是一个抽象的工具类,里面定义了SmsRecycler和MmsRecycler,用于删除陈旧的消息,或者删除超过信息数量限制的信息。使用方法都是Recycler.getSmsRecycler.deleteOldMessages(context) 或者Recycler.getMmsRecycler.deleteOldMessages(context)
- SmileyParser.java---把标点式的表情符号转化为图形的表情,比如把?用图标笑脸来代替。
- DownloadManager.java---不要被名字骗到,它并不是真正意义上的下载管理器,因为它并不负责任何与下载文件过程或下载文件的管理。它是用于管理与下载相关的配置信息,比如是否是自动下载,以及下载过程的各种通知,比如Notification Bar和Toast提示等。
- RateController.java
- SendingProgressTokenManager.java
- AbstractRetryScheme.java
- DefaultRetryScheme.java—这二个类是实现一种Retry机制,因为信息的发送与接收会受到环境的限制,比如现在手机没信号,或是网络连接不成功,那么就会把信息放到Pending队列里面,等一段时间再重新尝试发送与接收。这里的二个类就是为了实现此Retry机制。
- HttpUtils.java—彩信发送与接收的最底层实现者,它负责用HTTP协议接收和发送彩信到MMSC彩信服务中心。
- MessageSender.java—像其名字所预示的那样,它是为了发送信息而封装的一个接口,它里面只有一个方法sendMessage(),UI层只需要调用实现了这个接口的类即可发送信息。
- MessagingNotification.java—专门负责在Status Bar上面做Notification,比如新接收到了信息,或是信息发送失败,或是接收失败等。它被UI层,和底逻辑层共用着。
- MmsMessageSender.java—继承自MessageSender,专门用于发送彩信。它并不是做发送的事情,而是做一些错误检查和前期准备工作,然后启动TransactionService来做发送相关的事情。
- NotificationTransaction.java—继承自Transaction,负责接收彩信和更新通知(Notification)。当有一个新彩信时,Frameworks会先发出一个短信,称作彩信通知(NotificationIndication),其内含有彩信相关的信息(MMSC, 彩信的ContentLocation(URL)等),之后是由应用程序自己去MMSC用ContentLocation取彩信。这个NotificationTransaction就是专门用于处理彩信通知的,它会从MMSC上取出彩信数据(Pdu),把它写入数据库中,然后更新Notification。需要注意的是,只有彩信的设置是自动获取(“auto retrieve”)时,它才会去下载彩信,否则,它只处理彩信通知(Notification Indication),而不去下载彩信。
- Observable.java—里面定义了观察对象,Transaction是它的一个子类,其他的实体Transaction都是观察对象,里面有一个列表保存着观察者的引用,当一个Transaction完成时,或是有异常时就会调用notifyObservers()方法来把状态通知给观察者。
- Observer.java—观察者,TransactionService实现了这个接口。它是所有Transaction的观察者,以监听他们的状态和处理结果,因为所有的Transaction都 是异步的,所以才用观察模式来通知Transaction的处理结果。
- PrivilegedSmsReceiver.java—继承自SmsReceiver短信收信的事件监听者,负责监听新短信事件Android.provider.Telephony.Intents.SMS_RECEIVED_ACTION(“android.provider.Telephony.SMS_RECEIVED”);当接收到这个Intent时表明有一个新短信。它会唤起SmsReceiverServier来处理短信。
- ProgressCallbackEntity.java
- PushReceiver.java—一个BroadcastReceiver专门用于接收彩信事件Android.provider.Telephony.WAP_PUSH_RECEIVED_ACTION(“android.provider.Telephony.WAP_PUSH_RECEIVED”),它会先做一些预处理,然后启动TransactionService,TransactionService又会创建NotificationTransaction来处理这个彩信通知。
- ReadRecTransaction.java
- RetrieveTransaction.java—继承自Transaction,用于主动获取彩信数据。当彩信设置为非自动获取时,需要用户触发获取,TransactionService会创建一个RetrieveTransaction来获取彩信数据(Pdu),存入数据库,更新Notification等。
- RetryScheduler.java
- SendTransaction.java—继承自Transaction,用于发送彩信数据。
- SimFullReceiver.java
- SmsMessageSender.java—发送短信的封装,继承自MessageSender。它会启动SmsReceiverService来发送。
- SmsReceiver.java—是一个BroadcastReceiver,不要被其名字唬到,它并不负责接收新短信通知,相反,它用于发送信息,接收发送信息请求,并唤起SmsReceiverService来处理发送。这里可能是Android命名规则的原因,Android里的四大组件都喜欢把其组件的名字加上,比如ComposeMessageActivity,是一个Activity,TransactionService是一个Service,而这里SmsReceiver是一个BroadcastReceiver,它与接收短信(receiving Sms)没有关系。当然了,这完全是一个糟糕的命名。
- SmsReceiverService.java—它是一个Service,专门用于处理短信的发送与接收。它是由SmsReceiver和PrivilegedSmsReceiver监听事件,然后启动它的,自己并不会监听Intent事件。
- SmsRejectedReceiver.java
- SmsSingleRecipientSender.java—继承自SmsMessageSender,它针对一个收信人,调用Frameworks层接口发送信息,对于Mms应用来说,这是发送短信的最后一站,对就是说对于应用来说,它会把短信发送出去。
- TransactionBundle.java—Transaction所用的一个数据结构,用于给Transaction传送数据。
- Transaction.java—各种Transaction的基类,它里面定义了二个方法getPdu(),sendPdu()这二个方法是从MMSC取彩信数据,和向MMSC发送数据。它是对HttpUtils的一层包装。
- TransactionService.java—是一个Service,接收各种Transaction请求,然后处理Transaction。每个Transaction都 会开启新的线程异步的处理,所以当处理完成时又会通过Observer来通知TransactionService。
- TransactionSettings.java—彩信相关配置信息的数据结构,比如MMSC,Proxy,Port等。请求方可能会提供这些数据,如果提供就使用;否则就会从Telephony数据库加载默认的数据,这些数据与运营商和APN的设置有关。
- TransactionState.java—标识每一个Transaction处理情况的数据结构,很简单,只是标明处理成功还是失败,用于Transaction回调Observer(TransactionService)时用。
- MmsApp.java---Mms Application会在应用进程启动的时候做一些必要的初始化工作,比如配置,下载,联系人,对话,Smiley解析器和通知等。
- MmsConfig.java---管理Mms的一些常用配置,比如彩信大小上限,彩信图片尺寸上限,收信人的个数上限等等。这些配置信息是保存在在res/xml/mms_config.xml里面。MmsApp在初始化时会调用MmsConfig.init(),在这里面会调用loadMmsSettings来解析mms_config.xml从而得到所需要的配置信息。其他的模块只通过MmsConfig来访问这些配置信息。
- LogTag.java---有关日志跟踪信息的控制。它可以方便的控制日志输出级别。但是实际上整个Mms代码中使用这个LogTag的地方并不多。
信息的发送,对于Mms应用程序来讲主要就是在信息数据库中创建并维护一条信息记录,真正的发送过程交由底层(Frameworks层)函数来处理。
总体的来讲,当信息创建完成后,对于信息通常有三个去处,一个是放弃这个信息,也就是用户不想要此信息,一旦选择,信息将不会被保存;第二个去处就是保存为草稿;最后一个去处就是发送此信息。
当点击了发送后,UI层暂不会有变化,UI层要监听负责发送的各个类的回调信息和数据库的变化信息来更新UI。信息发送的第一站是WorkingMessage,它会先处理一下信息的相关内容,比如刷新收信人(Sync Recipients)以保证都是合法收信人,把附件(Slideshow)转成可发送的彩信附件Pdu(SendReq),makeSendReq。然后针对,不同的信息类型(短信,彩信)调用不同的处理类来处理。处理的流程也比较类似,都是先把消息放到一个队列中,然后启动相应的Service来处理。Service会维护信息队列,然后处理每个信息。短信是由Frameworks中的SmsManager发送出去,而彩信是通过Http协议发送。
短信发送在WorkingMessage拿到一个要发送的消息后,做了简单处理(刷新收信人),然后就会对短信和彩信彩取不同的处理流程。对于短信,WorkingMessage除了刷新联系人外,不会再做其他的事情,它会创建SmsMessageSender并调用其sendMessage()方法来发送信息,相关的参数收信人地址(是以分号分隔的一串字符),信息内容和所在对话的ID(thread id)在构造SmsMessageSender对象是传入的,构造完成后,直接调用其sendMessage()方法即可,接下来SmsMessageSender会处理所有的事情。
在交由SmsMessageSender处理之前,WorkingMessage会回调UI一次,以让UI刷新收信人编辑框和信息文本输入框。
SmsMessageSender的主要任务就是,把信息进行按收信人拆分,也就是说,短信是要给每个收信人都发一封,虽然你可能只编辑一个短信,但是当收信人不只一个时,就变成了多条短信,就要发出多条短信,要给每一个收信人都发一封短信。因此,SmsMessageSender的第一个任务就是分析收信人地址,得到收信人的个数,然后把信息按每个收信人都放入待发送的队列中。这样就得到了一个短信发送队列,短信的数目就是收信人的个数。事实上,SmsMessageSender的工作仅此而已,当把信息都放入发送队列后也就是写进数据库,然后信息的状态是正在发送中,它会发送Intent唤起SmsReceiverService来处理队列,它的工作就完成了,sendMessage()也就此返回。SmsMessageSender的sendMessage()返回后,WorkingMessage会再次回调UI的接口,因为此时短信已被写入数据库,所以UI会刷新信息列表,显示刚刚的短信,这时的状态应该是正在发送中,因为是从待发送队列中拿到的。从这以后,发送流程的类不会再直接与UI进行通信,发送服务SmsReceiverService等会直接更新数据库中短信的状态,而UI会监听数据库的变化,一旦信息数据发生变化,UI就会刷新列表中的消息,更新状态,比如将发送中变成已发送,或是标明发送失败等,而这些状态都是发送服务在更新。
SmsReceiverService,不要被其名字虎住,它并不只负责接收信息,它是短信(SMS)处理的Service,负责短信的发送和接收,在得到发送短信息指令(ACTION_SEND_MESSAGE)后会从队列中读出第一个短信,然后创建SmsSingleRecipientSender对象,传入收信人地址,消息内容,所属的threadid和短信的Uri,并调用其sendMessage()发送这个短信。
SmsSingleRecipientSender会调用SmsManager的方法divideMessage()来把短信分成适合发送的几个部分,因为可能信息过长,不能一次发送完成,所以就需要分成几部分来分次发送。同时会把消息移动到Outbox。然后会针对分割的每一部分都会创建二个PendingIntent,这二个PendingIntent都是给底层用的,一个用于当短信被发送出去时广播出来,另一个是在短信已被收信人接收到时广播出来。所以二个广播的作用是,一个可用于标识短信已发送,另一个则可以作为送达的通知。最后调用SmsManager.sendMultipartTextMessage交由底层来发送短信。
SmsReceiverService并不是自己去监听SEND_MESSAGE_ACTION和MESSAGE_SENT_ACTION的,而是由SmsReceiver来监听这二个广播事件,然后通过StartService再把这二个事件传送给SmsReceiverService进行处理。
信息已发送广播和信息已送达广播分别由SmsReceiverService监听和MessageStatusReceiver。它们收到广播后,会从Intent中取得详细的发送和送达状态,然后更新数据库中信息的状态(status),UI当发现数据库变化后,就会更新UI。
至此,一个短信发送完成。
彩信发送
彩信发送流程与短信不完全一致,WorkingMessage刷新收信人,生成彩信的可发送的Pdu—SendReq,接着会把彩信写入数据库,把要发送的SendReq也会写入数据库,后面会再从数据库中读取出SendReq,并标识为草稿;然后会构建MmsMessageSender,传入收信人和彩信的Uri,让其发送。这期间也会回调UI一次,以初始化收信人编辑框和信息编辑框。
MmsMessageSender先从数据库中读出彩信发送的Pdu—SendReq,Google的内置包com.google.Android.mms.*;里面封装了所有操作Pdu的方法,包括把Pdu写入数据库(PduPersister.persist()),从数据库中读取生成Pdu(PduPersister.load())。然后根据当前彩信的配置和其他信息对SendReq进行更新,比如设置Expiration,Priority,Date和Size等,把彩信移到Outbox,然后启动TransactionService来处理彩信。sendMessage()就此返回。WorkingMessage会再次回调UI的接口,因为此时彩信已被在数据库中,所以UI会刷新信息列表,显示刚刚的彩信,这时的状态应该是正在发送中。
TransactionService,与短信的SmsReceiverService类似,是负责处理彩信的服务,可以发送,接收等。对于TransactionService来讲,所有的需要处理的流程,无论是发送还是接收,都是一个Transaction。它内部有二个队列,一个是当前正在处理(processing)的Transaction,一个是待处理(pending)的Transaction。它维护这二个队列,并检查网络的连接,打开彩信网络连接,准备和检查环境,然后从待处理的队列中取出第一个,放入正在处理的队列中,并处理这个Transaction,也就是调用Transaction.process()。
发送彩信是一个SendTransaction,它的process()方法负责发送彩信,它会创建一个独立的线程来做,因此不会阻塞TransactionService,处理服务就可以再处理其他的Transaction。它会先从数据库中取出彩信Pdu,M-Send.req,(SendReq),更新一些字段,比如date,然后调用其父类Transaction.java中的方法sendPdu来把SendReq发送出去,sendPdu()会返回发送的结果(send confirmation)。Transaction.sendPdu()会先设置好网路,然后直接调用HttpUtils中的httpConnection()方法,用HTTP把彩信发送出去,同时取得返回消息(Response)给SendTransaction。SendTransaction会检查发送结果,返回结果(Send Confirmation),分析状态并更新至数据库(比如发送失败或发送成功)。UI会监听到状态变化,并更新信息列表。
到此,一个彩信发送完成。
前面有提到过TransactionSettings,它是对于一个处理流程的相关配置信息,里面含有MMSC(Multimedia Message Service Center),Proxy和ProxyPort。这些信息,特别对于发送和接收来说是十分重要的。因为对于手机的信息,并不是手机直接把信息发送到接收人的手机上,而是直接发给服务中心,后面就是由服务中心再把信息发送给对应的接收人的手机上。对于彩信也是这样,HttpUtils通过HTTP协议把彩信发送给MMSC,它是一个URL地址,之后对于发送方来讲,彩信就发送完了,彩信服务中心(MMSC)会处理接下来的发送过程,服务中心是与手机运营相关的,它由运营商来提供。对于Mms发送彩信,是不会特意指定TransactionSettings的,也就是说它不会指定MMSC和Proxy,那么TransactionService就会用系统默认的MMSC,Proxy作为TranscationSetting,MMSC,Proxy和ProxyPort需要从Telephony数据库中查询出来,它们是与具体手机的APN设置和具体的运营商相关。所以,这里如果想要改变彩信的配置信息,只能更改APN系统设置来完成。
而短信的发送就不涉及SMSC(短信服务中心),因为Frameworks中的工具已经封装好了SmsManager提供了几个发送短信的方法,可能它会去处理SMSC相关的东西。
总结,可以看出数据库在信息的发送过程中扮演了重要的角色,当信息离开编辑器后就马上写入了数据库,发送过程中的各个类都是先从数据库中加载信息,然后做相应处理,然后写回数据库或是更新状态,然后再交由下一个流程来处理。而所谓的Pending Message Queue其实没有相应的数据结构,它们都是数据库中的信息且状态是待发送而已。所以信息离开编辑器后就被写入了数据库,只不过状态一直在改变,从发送中到已发送,或发送失败,或如果Telephony服务不可用会仍处在待发送,但对于UI页面来讲可能没有那么多状态,它可能只显示发送中,已发送和发送失败。
接收信息流程信息的接收工作是由底层来完成的,当有一个 新的信息时底层完成接收后会以Intent的方式来通知上层应用,信息的相关内容也包含在Intent当中,Android所支持的信息Intent都定义在android.provider.Telephony.Intents里面。
短信接收,对于上层应用程序来讲就是要处理广播事件SMS_RECEIVED_ACTION,它是由Frameworks发出告诉上层有新的SMS已收到。在Mms中,是由PrivilegedSmsReceiver来处理,它收到SMS_RECEIVED_ACTION(Android.provider.Telephony.Intents.SMS_RECEIVED_ACTION=”android.provider.Telephony.SMS_RECEIVED”)后会启动SmsReceiverService来做具体的处理。
SmsReceiverService会先检查短信的类型,如果是Class0短信,直接在GUI中显示,不做任何其他的处理,也即不会存储到数据库中,也不会在Notification Bar中做Notification。
对于其他短信,会进行替换现有的消息,或是当作新消息插入。原则就是如果在数据库中已有的短信中,与新来的短信的原始地址和协议标识都一样,那么就把其替换成新进的短信,否则就当作新短信插入。
具体的替换流程:先用新进的短信生成一个ContentValues,再用短信的地址和协议标识当作条件到数据库中去查询,如果查到了,就替换,否则就存储。
存储的流程,也是先生成一个CotentValues,然后取出短信的Thread Id和地址,地址要与联系人数据库同步一下,以保证是能识别的地址。如果Thread Id不是合法的,那么就用同步过的地址尝试重新生成Thread Id,尝试5次。然后把刷新过的Thread Id放到ContentValues中,把ContentValues插入到数据库中。如果设置为把信息存储到SIM卡,还要调用SmsManager把信息拷贝到SIM卡上。计算短信的大小,并更新至数据库。删除过期的短信,和超过数量限制的短信,然后返回插入后得到的短信Uri。
最后,对于替换或插入的短信,用Uri去StatusBar做Notification。
GUI在刷新列表时也能得到新短信,因为短信已经被存储到数据库中。
彩信的接收过程与短信略有不同,它主要是由应用程序负责从彩信服务中心(MMSC Multimedia Messaging Service Center)下载彩信信息。大致的流程是Frameworks会先发出一条短信,告知应用程序有一个彩信,短信中含有一些信息比如过期日期,发送者手机号码,彩信的URL等,然后应用程序自行通过HTTP取回URL所指的彩信内容。具体的流程为:
Telephony Frameworks会先发出一个Intent:Android.provider.Telephony.Intents.WAP_PUSH_RECEIVED_ACTION=”android.provider.Telephony.WAP_PUSH_RECEIVED”告知上层应用有一个彩信来了。这个Intent中会含有一个”data”byte数组(通过byte[] data = intent.getByteArrayExtra(“data”)来获取),这个Byte数组是关于这个彩信的一些信息的描述,它是一个NotificationInd,里面含有彩信的一些信息,比如发送者手机号码,彩信的ContentLocation(URL)。之后是由应用程序来决定如何做下一步的处理。
在Mms中是由transaction.PushReceiver.java来接收WAP_PUSH_RECEIVED_ACTION,接收到彩信通知Intent后,它会做一些预处理,把data字段取出来,用Pdu的工具解析成为GenericPdu,然后转化为NotificationInd,并把它写入数据库,然后会启动TransactionService来做进一步的NOTIFICATION_TRANSACTION处理,同时把这个NotificationInd的Uri也传过去。
TransactionService被唤起,在其onStartCommand中会处理一下把PushReceiver所传来的Intent放入自己的MessageQueue中,然后在Handler.handleMessage()中处理TRANSACTION_REQUEST时处理NOTIFICATION_TRANSACTION。先是加载默认的一些彩信相关的配置信息,主要是MMSC,Proxy和Port,这些都是与运营商相关的信息,可以通过APN的设置来更改。TransactionService用PushReciver传来的NotificationInd的Uri和加载的配置信息TransactionSettings构建一个NotificationTransaction对象。之后,TransactionService检查其内的二个队列,或是加入Pending队列,或是直接处理(加入到正在处理队列),处理也是直接调用NotificationTransaction.process()。
NotificationTransaction的process()方法是继承自父类Transaction的方法,它只是简单的开启一个新的线程,然后返回,这样就可以让Service去处理其他的Transaction Request了。
在线程中,首先从DownloadManager和TelephonyManager中加载一些配置信息,是否彩信设置为自动获取(auto retrieve),以及Telephony是否设置为数据延迟(DATA_SUSPEND),然后会采取不同的措施,再从NotificationInd中取出彩信的过期日期。如果配置为不取数据(更确切的说,是不现在取数据),那么就先给DownloadManager的状态标记为STATE_UNSTARTED,再给MMSC发送一个Notify Response Indication,之后结束处理,函数返回,彩信的通知处理流程到此为止。用户可以通过操作UI,用其他方法手动下载彩信,这个会在后面详细讨论。
如果设置为自动获取或者数据传输是畅通的,那么就把DownloadManager状态标记为START_DOWNLOADING并开始下载彩信数据。彩信的获取是通过HTTP到彩信的ContentLocation(URL)取得数据。先是调用父类方法getPdu(),传入彩信的URL,最终调用HttpUtils的httpConnection方法发送HTTP GET请求,MMSC会把彩信数据返回,作为getPdu()的返回值返回。拿到的是一个byte数组,需要用Pdu的工具解析成为GenericPdu,然后用PduPersister把其写入数据库,再把彩信的大小更新到数据库,到这里一个彩信的接收就算完成了。剩下的就是,因为已经获得了彩信的数据,所以要把先前的通知信息(NotificationInd)删除掉,然后更新一下相关的状态,给MMSC返回Notify Response Indication,结束处理。因为数据库已经有所改变,所以UI会收到ContentChanged事件,刷新UI列表,新信息就会显示出来。
如前所述,如果彩信配置设置为不自动获取,那么UI刷新了后就会显示彩信通知:到期日期,彩信大小等,并提供一个”Download”按扭。用户可以点击按扭来下载彩信内容,点击按扭后,会启动TransactionService,把彩信通知的Uri,和RETRIEVE_TRANSACTION request打包进一个Intent传给TransactionService。TransactionService,像处理其他的Transaction一样,都是放进自己的MessageQueue,然后加载默认的TransactionSettings,构建RetrieveTransaction对象,然后处理调用RetrieveTransaction.process()。
RetrieveTransaction也是继承自Transaction,其process()也是创建一个线程,然后返回。在线程中,首先它用Pdu工具根据Uri从数据库中加载出彩信通知(NotificationInd),从NotificationInd中取得彩信的过期日期,检查过期日期,如果彩信已经过期,那么给MMSC发送Notify Response Indication。把DownloadManager状态标记为开始下载,然后如果彩信已过期,标记Transaction状态为Failed,然后返回,结束处理流程。如果一切正常,会用getPdu()从彩信的ContentLocation(URL)上面获取彩信内容,它会用HttpUtils.httpConnection()通过HTTP来获取,返回一个byte数组。用Pdu工具解析byte数组,得到GenericPdu,检查一下是否是新信息,是否是重复的信息,如果重复,标记状为失败,然后返回,结束处理。如果是新信息,先把GenericPdu用PduPersister写入数据库中,更新信息大小和ContentLocation(URL)到数据库中,到这里一个彩信其实已经全部获取完了。接下来就是发送收到确认信息给MMSC,标记处理状态为成功,结束处理。这时UI应该监听到数据库变化,并刷新,新信息应该会显示给用户。
总结,与信息发送类似,数据库在接收信息过程中也扮演了重要角色,信息接收到后进行解析,然后就写入数据库,与发送不同,接收的信息没有那么多状态,一旦写入了数据库就意味着信息接收已经成功,UI也是只监听数据库的变化,一旦有变化立刻刷新显示信息。
草稿管理当编辑完一条信息后,如果在没有发送的情况下退出编辑页面,那么信息会自动保存为草稿。也就是在ComposeMessageActivity的onStop()时,如果还没有发送,那么就会调用WorkingMessage.saveDraft()来把信息保存为草稿。期间也会检查一些条件,比如消息是否已被标识为放弃,或是是否为空(isWorthSaving),如果一切正常会saveDraft()并会用Toast来告知信息已保存为草稿。
草稿的保存也是针对不同的信息而不同,短信和彩信的流程有所不同。
保存短信为草稿WorkingMessage会先取出短信内容,然后开启一个新的线程去做接下来的事,WorkingMessage.saveDraft()也会就此返回。在线程中,会先确保ThreadId的正确,如果没有正确的ThreadId,就不会保存。接着把信写进数据库,把Type标识为Draft。最后会删除这个Thread所拥有的彩信草稿,因为一个Thread中只能有一个草稿,所以如果有了新的短信草稿那么就要删除旧的彩信草稿,同理,后面保存彩信草稿的时候也会删除短信草稿的。
保存彩信为草稿与保存短信类似,ComposeMessageActivity在onStop时调用WorkingMessage.saveDraft();WorkingMessage.saveDraft()先会刷新收信人信息,然后会创建一个彩信的数据结构SendReq,然后启动线程做其他的事,saveDraft()也就此返回。在线程中,先是保证是一个合法的Thread,也就是threadid要正确。同时也要把这个Thread标志为有草稿,这个是由一个DraftCache在管理,它是一个HashMap,来标识哪些Thread含有Draft。如果这个Thread以前没有附件,那么就为它创建附件,也就是把SendReq写入数据库;相反,如果已有了附件,那么就更新数据库,把SendReq和Slideshow,日期更新成为当前信息的内容。最后删除掉已有短信草稿。
这里要注意的对于彩信的操作都由Frameworks中的com.google.Android.mms.*包里面提供的类和工具来完成的,它里面会提供Android所支持的彩信的数据结构SendReq,把数据(Text,Medias,Files)放入SendReq的方法PduPart,PduBody,把SendReq写入数据库和从数据中读取SendReq—通过PduPersister。客户端的应用程序,只是创建SendReq,用提供的方法把数据写入SendReq中,用PduPersister来写入数据库和从数据库中提取,最后用HTTP协议把SendReq发送出去。
同时还有一个专门的类DraftCache用来管理哪些Thread含有草稿,它的内部是一个HashMap,可以标识哪些Therad含有草稿。所以,在对草稿操作的地方都会用到DraftCache,如果一个Thread含有草稿,就需要把它的ThreadId标识为有草稿;如果一个Thread的信息已发送出去,就要把它标识为不含有草稿。
传统的以文件夹方式管理信息都会有一个专门用于存放草稿的文件夹叫草稿箱。每次编辑信息,无论是发给哪个人,都可以放入这草稿箱。但是这里也可以发现,与传统的以文件夹方式不同,Android中的Mms的草稿是每个Thread一个,而且只有一个,换句话说,不可能存储太多的草稿。因为Android中的Mms是以对话Thread方式来管理信息的,而一个Thread,一次对话,只应该有一个没“说完”的话,所以这种设计也是合常理的。
MMS支持 Composing and editing MMS在Android Mms应用里面的具体实现形式,或数据结构是SlideshowModel,它是一个每个节点为SlideModel的ArrayList,SlideModel是一个Model的List,也就是它可以接收任何Model的子类,Audio,Video,Image和Text都可以放到SlideModel上面。SlideModel主要用于管理其上面的各个媒体,比如它们的布局,它们的播放控制,而SlideshowModel主要用于管理所有的附件,比如把所有的附件转化成为Android的MMS协议的数据类型Pdu,以及从Pdu转化成为SlideshowModel。Pdu是实现了MMS协议的标准格式,它可以直接的发送给MMSC,从MMSC取回来的也是一个Pdu格式的数据。应用层Mms不需要关心Pdu的具体实现方式,Android中有一个内部的包com.google.android.mms.*下面的类都是专门用于处理Android平台上的MMS。里面提供了工作可以把应用层的数据,比如媒体文件等,进行包装成Pdu,再把Pdu分解成为媒体文件。Pdu的数据结构包括PduBody,这个是用于存放多媒体文件的地方,其里面是PduPart的集合,每个PduPart代表一个文件。PduPersister用于操作这些数据结构,包括写入数据库,从数据库中读取等。
SlideshowModel或俗称幻灯片是应用层的MMS的实现形式,或者它是应用层MMS用来创建,编辑,显示和管理多媒体的一个数据结构。创建和发送MMS的时候,就是创建一个SlideshowModel,构建MediaModel,TextModel等加入到SlideshowModel中。在发送时,SlideshowModel会把其内的媒体文件取出来,转化为PduPart放入PduBody中。收到信息后从PduBody中取出PduPart,还原成媒体文件,生成MediaModel,加入到SlideshowModel中,也就是还原为幻灯片。应用拿到幻灯片后可以做显示和播放。
附件类型 关于附件类型,Mms应用中所有的MMS都有一个幻灯片,其内含有所有的附件文件。但是Mms做了一些特殊的处理,对于一个MMS信息,它的附件类型分为IMAGE, AUDIO, VIDEO,和SLIDESHOW,这些从添加附件对话框的列表中可以看出,而且展现方式也有所不同。但是实际的实现上面并没有这么多的类型,只有一个SlideshowModel,所有的附件都在里面。它处理的规则是这样的,如果只添加了一个媒体(image, audio和video)时,会把类型设置为相应的媒体类型,而只有在附件对话框中明确选择添加幻灯片时并且添加了多张幻灯片后,附件类型才会是幻灯片。这个附件类型只在给MMS添加附件时和发送MMS前有效,主要用于在消息列表中如何展示媒体文件,如果是具体媒体类型,就直接显示,否则显示为幻灯片,这个附件类型仅存在于应用中显示媒体所用,并不会在发送出去的Pdu中有痕迹。当收到MMS后,也是根据转化后的SlideshowModel里面的内容来推测出附件类型,然后再做显示。所以,对于一个MMS来说它始终都有一个SlideshowModel,用户所感受到的附件类型仅是附件媒体显示上面的一个处理而已。
创建和编辑MMS 与传统手机不同,创建MMS并不需要特殊的方式。因为Mms应用对MMS和SMS并不做严格的区分,而是以统一的对话中的一个消息来对待,所以MMS与SMS的区别也很简单,就看一个消息中只否有附件(WorkingMessage.hasAttachment())。创建MMS也十分简单,只需点击Composer而的Attach菜单添加媒体即可。在列表中选择image, audio和video后就只有一个媒体文件,都会跑到其他的Activity去选择文件,然后会返回其Uri给Composer,Composer会调用WorkingMessage.setAttachment()来做具体的添加,用Uri创建MediaModel然后加入到SlideshowModel中,并设置类型。另外,如果选择了Attach幻灯片,就会直接进入编辑幻灯片的而面,可以添加删除幻灯片页,给幻灯片页加媒体文件,设置布局等,之后Composer会把SlideshowModel显示出来,此时的附件类型也是SLIDESHOW,这些都是通过WorkingMessage.load()来完成的。
WorkingMessage在把媒体加到幻灯片里以后,就会回调一个接口 onAttachmentChanged(),Composer实现了此接口,这个接口主要用于通知Composer附件已发生变化,刷新UI以正确显示附件。Composer会创建AttachmentEditor来显示附件的内容,因为所有的附件都放在Slideshow里面这个Slideshow在WorkingMessage中,可以通过WorkingMessage.getSlideshow()来获取。AttachmentEditor会根据Slideshow里面的内容来创建不同的View以展示不同的附件,如果Slideshow中只有一个Video,Audio或Image,就直接创建VideoAttachmentView,AudioAttachmentView或ImageAttachmentView,而对于幻灯片中页数大于1时就会创建SlideshowAttachmentView。还有相应的按扭可以用来编辑,替换或删除,对于单个媒体有查看/播放,选择后可以查看原图和播放音频视频,替换可以重新重选择一个附件,删除会移除掉附件;对于Slideshow有编辑和删除,编辑会直接进入幻灯片的编辑页面,那里可以一页一页的对每页幻灯片进行详细的编辑,删除会移除掉附件。
编辑完附件后有三种处理方式,一个是发送信息,一个是保存为草稿另一个就是放弃信息。发送信息和保存草稿都会对幻灯片进行打包,转成Pdu,并保存到数据库,之后的幻灯片都需要从数据库加载并把Pdu解包成为SlidehshowModel。
Packaging and unpackaging MMS 要发送信息前,或是保存草稿时,都需要把SlideshowModel进行打包生成Pdu格式,并保存至数据库。这个称为MMS的打包(Packaging),是由SlideshowModel.makePduBody()方法来完成,它会把幻灯片里面的内容一个一个的取出来,转成一个PduPart,再 放入PduBody中,以生成PduBody,一个媒体对应一个PduPart,同时还可以设置PduPart的属性以描述媒体的文件,比如ContentType,这是一个用于标识媒体MIME类型的字串;Filename文件的名字; ContentLocation文件的路径。这些信息都用于描述PduPart中数据的元信息(MetaData),也就是数据具体是什么,以便让解包的时候对数据进行正确的处理。
之后PduPersister会通过其persist()方法把PduBody存入到数据库中,它会把PduPart中的描述性信息作数据库字段写入,把文件存储在TelephonyProvider文件夹下面(/data/data/Android.providers.telephony/app_parts),并把存储后的路径作为_data字段写入数据库,这样一条MMS的数据就都写入了数据库中。这以后,MMS的数据都是从数据库中加载,所以原SlideshowModel中的数据库不再有效,如Uri在原SlideshowModel中可能指向一个文件,或是其他数据库,在PduPersister.persist()之后就不再有效了。
当PduPersister.persist()之后,MMS的附件就都从数据中加载,PduPersister.load()会从数据库把数据加载成为一个PduBody,SlideshowModel的方法createFromPduBody()就是用于把PduBody转化成为一个SlideshowModel,从PduPart取出媒体信息以得到正确的媒体格式,和相关信息,可以通过Uri来获取具体文件(流)。
接收到的MMS过程也差不多当NotificationTransaction或RetrieveTransaction用HttpUtils从MMSC获取到MMS数据后会用PduParser来解析数据生成Pdu,再用PduPersister.persist()把其写入数据库,之后会再从数据库中加载。
SMIL语言支持 对于每条MMS还有一个很重要的数据就是SMIL语言,SMIL是同步多媒体集成语言的简称(Synchronized Multimedia Integration Language),它与HTML文档很类似,是W3C(World Wide Web Consortium)组织规定的多媒体操纵标准语言。MMS也是用它来管理和播放多媒体。来看一个具体的SMIL语言实例:
- <smil xmlns="http://www.w3.org/2000/SMIL20/CR/Language">
- <head>
- <layout>
- <root-layout width="360" height="615"/>
- <region id="Image" width="347" height="260" top="14" left="7" fit="meet"/>
- <region id="Text" width="326" height="320" top="281" left="7" fit="scroll"/>
- </layout>
- </head>
- <body>
- <par dur="60s">
- <img src="0.jpg" region="Image"/>
- <text src ="0.txt" region="Text"/>
- </par>
- <par dur="60s">
- <text src ="1.txt" region="Text"/>
- </par>
- <par dur="60s">
- <text src ="2.txt" region="Text"/>
- </par>
- <par dur="60s">
- <text src ="3.txt" region="Text"/>
- </par>
- <par dur="60s">
- <text src ="4.txt" region="Text"/>
- </par>
- </body>
- </smil>
它主要记载着用于幻灯片的布局信息。这个SMIL语言就是用于幻灯片布局的,也就是说SMIL会像HTML文档布局网页那样来说明如何布局幻灯片,它有这些TAG:head, layout, body, par,head是头信息,里面有TAG layout用来说明这个幻灯片是如何布局的,具体的它用一些子TAG如root-layout, region等来说明幻灯片中的每一个元素如Image或Text如何布局。TAG body中列出了幻灯片的所有媒体元素和详细内容,比如image, audio, text等,每一par是一页,它的子TAG说明这一页有哪些内容,当然SMIL语言还有很多内容可以参考Wikipedia上的讲解。
当打包幻灯片时,也就是把SlideshowModel转化为Pdu时,会根据SlideshowModel的内容生成一个SMIL语言,通过SmilHelper.getDocument()来生成SMIL文档,把其加入到PduBody中并作为第一个PduPart,它的ContentType(MIME)是application/smil,它的内容就是SMIL文档。需要注意的是SMIL文档总是会在PduBody的第一个Part,并且它直接把文档内容写到PduPart中,而不是以文件的形式存在。
当解包的时候,会先取出SMIL文档,对其进行解析,生成幻灯片。
因为SMIL是一个标准的文档,所以W3C有其相应的规范,也有相应的库来解析和生成。在Mms应用中可以看到这样的二个Package: org.w3c.dom.*和com. Android.mms.dom.*;其中org.w3c.dom是SMIL语言的一些标准库,而com.android.mms.dom.*;是对org.w3c.dom一些标准接口的实现,或者说是为了Mms应用而做的一些适配。那么在com.android.mms.model.*里面的一些类也是根据SMIL标准而写的,比如SmilHelper就是专门用于解析SMIL文档和生成SMIL文档,当然它会用到前面提到的二个Package里面的东西。还有如ImageModel,TextModel和RegionModel也都是基于SMIL标准的,比如它们分别 对应SMIL文档中的标签img, text和region。
当然,这都是具体的终端应用的实现,可能不同的应用会有不同的方式,但发送出去的和接收到的都应该是标准的Pdu,而SMIL文档仅是一个其中一个PduPart而已。
*具体出处不详,我从http://blog.chinaunix.net/uid-23392298-id-3322423.html转载