1. Mms 概述
MMS为Multimedia Messaging Service的缩写,中文译为多媒体短信服务。中国移动
“ ” 公司把它定名为 彩信 ,可以用于传送文字、图片、动画、音频和视频等多媒体信息。
。MMS的工业标准是由两个组织,WAP Forum(WAP论坛)和3GPP 所制订的。因此,
MMS是设计成可以在WAP协议的上层运行,它不局限于传输格式,既支持电路交换数据
格式(circuit-switched data),也支持通用分组无线服务GPRS格式(general
packet radio service)。其工作原理为利用高速传输技术EDGE(Enhanced Data
rates for GSM Erolution是一种提高数据速率的新技术,是GSM向第三代移动通信系统
IMT- 2000过渡的台阶。它也被称为"GSM 384",因为这种技术能使数据速率由目前的
9.6kbit/s提高到384kbit/s,这种速率可以支持语音、因特网浏览、电子邮件、会议电视
等多种高速数据业务)和GPRS的支持下,以WAP(无线应用协议)为载体传送视频、图片、
声音和文字。
基本功能
多媒体信息业务系统一般具有以下几个功能。
1.多媒体消息的发送和接收。手机终端合成多媒体消息后,可以向网内的所有合法用
户发送多媒体消息。由MMSC对多媒体消息进行存储和处理,并负责多媒体消息在不同
MMSC之间的传递等操作。同时接收方用户可以从MMSC接收多媒体消息。
2.提供对非MMS终端的支持。这由非多媒体消息支撑系统来完成。以非MMS终端接
收多媒体消息的流程为例,非MMS终端用户接到SMS通知后,可以通过其他手段访问多媒
体消息,如E-mail、WAP、www浏览等方式。
3.在网络承载方式上,现阶段支持基于CSD和GPRS的承载方式,未来将支持3G承载
方式。
4.多媒体消息业务支持点到点的业务和点到多点的业务。点对点多媒体消息业务指发
方和接收方是一个终端或应用系统;点对多点多媒体消息业务指接收方是多个终端地址。
在一次多媒体消息发送过程中,可以指定多个接收终端地址。
5.对MMS增值应用的支持。多媒体消息系统除了支持一些现有的应用系统(如E-mail
系统)以外,还应提供开放的、标准的API接口,支持增值应用开发。
编辑本段实现方式
目前多媒体消息服务的业务采取3种方式进行:
第一种是发送方和接收方都是使用带有MMS功能的手机,即可直接传输。
第二种是发送方拥有带MMS功能的手机,而接收方是普通手机。此时,接收方可在移
动梦网网站上申请一个以自己手机号码为信箱名的电子邮箱,当发送方发送一条MMS后,
接收方手机会收到一条来自移动梦网的MMS到达信箱的短信通知。接收方就可登录信箱查
阅MMS了。
第三种是使用普通手机的发送方可在互联网上直接给使用MMS手机的接收方发送
MMS。
与SMS的比较
MMS与SMS在消息发送方式上都是相同的:都是存储一转发业务,即消息不是直接送
达用户。而是先送至消息中心,再经过消息中心转发到用户。但是,MMS与SMS也存在着
很大差异:
1.SMS作为一个承载可以开展各种应用,如:邮件到达通知、天气预报、新闻、铃声
图片下载、彩票、游戏、证券等。MMS作为一个应用的承载平台,除了上述应用之外,还
可以提供更丰富的应用。
2.在承载方式方面,SMS是使用GSM的信令通道。由于信令通道的传输能力有限,使
得SMS不可能传输大数据量,因而基于SMS的应用不能任意开展。须是小数据量的应用。
而MMS是基于WAP业务的,走数据通道,其传输能力在CSD方式下可以达到9.6kbit/s,
在GPRS方式下最大可以达到184kbit/s,在3G下可以达到2Mbit /s,给应用的开展提供
了很大的便利。用户可以随心所欲地发送和接收数据,而不再受带宽的限制。
3.在内容能力上,SMS只能发送和接收文本信息,每条消息最大只能携带140字节的
字符信息或者是70个汉字信息。尽管EMS(Enhanced Message Service,增强型短消息
服务)可以支持图形、声音和动画信息,但这些数据的格式过于简单,用户对EMS的体验远
远不及传统Internet。而且由于每条SMS消息的大小不变。要传送超过140字节的信息必
须把EMS消息拆分成多条SMS,然后在手机上进行组合。更重要的是,EMS虽然也是
3GPP 的标准,但不是所有主流手机厂商都支持,在推广中然存在障碍,MMS可以支持
丰富的数据格式,包括主要的图形、图像声音、动画格式标准,用户的感受与传统
Internet完全一样,未来在带宽允许的情况下,还可以支持流媒体,大大提高消息内容的
丰富程度和表达能力。而且,MMS的消息大小突破了140字节的限制,从几十K字节到上
百K字节,用户几乎可以完全不受信息量的影响,在一个消息中就可以完整地表达自己的
思想。
4.MMS在网络结构上与SMS不同。MMS采用的是WAP事件的处理流程,由接收方主
动从MMSC取信息,相同于WAP的浏览或下载方式。
编辑本段MMS流媒体
可以传输音、视频的通用服务器有两种,都有各自的优缺点。分别是:标准WEB服务
器和流媒体服务器。标准WEB服务器使用HTTP协议。流媒体服务器使用两种协议提供媒
体服务。这两种协议分别是HTTP1.0或1.1以及MMS(MultiMediaServer)协议。流媒体
服务器使用的HTTP协议是经过修改的版本,扩展了语法命令以支持实时传输。这是普通
HTTP所不支持的。
使用两种协议提供媒体服务和WEB服务器有着显著区别。一个区别是在WEB服务器
上使用标准 HTTP协议的数据不需要一个特殊的服务器和软件进行浏览甚至下载。另外一
个区别是使用MMS(例如Microsoft Windows Media Services)的流媒体服务器通过
流形式提供媒体给使用者。流媒体服务器可以处理大量数据。
MMS是微软的私有流媒体协议。它的最初目的是通过网络传输多媒体广播、视频、音
轨、现场直播和一系列的实时或实况材料。使用这个协议的观众可以通过电脑观看电视图
像或音轨。微软为有网络连接的家用电脑使用者开发了免费软件。MMS建立在UDP或TCP
传输/网络层上,是属于应用层的。
使用TCP的MMS上URL是MMS://或者MMST://,如果是UDP的MMS使用MMSU://。在
低带宽的情况下推荐使用UDP连接。HTTP带有大量的头信息,UDP一般不能通过防火墙,
在有防火墙的情况下使用HTTP。TCP的无差错特性是非常诱人的,它的吞吐量比UDP小,
但是在下载MMS的时候TCP是不二的选择。
流程简介
以系统向手机发送信息为例,介绍一下多媒体信息服务的流程。在过程分析中省略了
有关无线接入的部分,只着重于的相关部分。
1.当有一条多媒体信息发往一个用户时,信息以WAP的WSP的协议进行编码。通过无
线网络传送到WAP网关。
2.WAP网关以HTTP协议与MMS-Relay进行通信,将文件内容传送给MMS-Relay。
3.MMS-Relay将文件送往MMS-C服务器。在服务器内多媒体信息的内容将转换成
MIME的格式。并存储在消息存储器(MMS-MessageStore)中。
4.服务器进行数据分析,从而得到路由信息、用户终端信息等。在分析过程中会调用
用户数据库中信息。系统将判断用户的终端是否能够支持MMS,并根据用户的终端的承载
能力(如显示分辨率、终端的容量等)进行不同的处理。例如,当用户终端不支持MMS时,
系统将把多媒体信息中的多媒体信息去掉,只把信息的文字部分以短消息的方式发给用户。
5.确认处理方法之后,系统通过被叫用户的MSIS-DN号码进行路由。MMS-Relay将
通过WAP网关与外部网络进行通信。在没有确认被叫用户已经接收了信息之前,该信息始
终保存在消息存储器中。运营商可以通过软件设定保存的时间长度。
6.系统服务器生成计费信息,传送给计费中心。
多媒体消息业务
(MMS-Multimedia Messaging Service)是在短消息业务基础上发展起来的一
种新型消息业务。MMS是第3代移动通讯标准化组织3GPP 制定的全球信息传送标准,是
一项全新的数据业务,用户可以像使用短消息一样收发更加个性化的多媒体消息。它将不
同的媒体,如文本、图片、照片、音频、视频等组合成一个多媒体消息进行发送。MMS信
息容量也大大增长,可以达到100kB左右。用户在终端上发送MMS操作也非常方便。和
SMS一样,MMS采用"存储转发"的技术,用户创建的信息能够自动、快速的在手机和手机
之间传送;信息的传送仍然按接收方手机号码进行定位;当接收方关机或暂时不在服务区
的情况下,信息将存储在多媒体消息中心(MMSC),直到能够正确达为止。
多媒体消息服务并不依赖于基础网络,它能够在第2代、第2.5代及第3代无线网络中
实施,无论GSM、GPRS、WCDMA网络都可以支持MMS业务。考虑到网络带宽、数据传
输速度,MMS业务将在当前GPRS网络上起飞,在未来3G网络中走向成熟。
手机终端合成多媒体消息后,可以向网内所有合法用户发送多媒体消息,由多媒体消
息中心MMSC对多媒体消息进行存储和处理,并负责将多媒体消息在不同MMSC之间的传
递等操作。同时接收方用户可以从MMSC接收多媒体消息。多媒体消息服务要求一个WAP
网关,一个数据传输网如电路交换网、GPRS或WCDMA网络,和一个短消息中心。目前,
MMS业务在实现时是以WAP作承载,短消息作提示通知,由MMS手机自动到多媒体消息
中心MMSC中去提取。在用户的眼里,多媒体消息像短消息一样是从多媒体消息中心主动
发送过来的。
2. Android MMS 源码流程
概述
MMS的收发操作借助于手机的短信机制,实际收发过程需要网络的APN支持,使用特定
的APN接入点实
现MMS数据的真实发送和接收;
源码流程
1 ) Telephpony.java getOrCreateThreadId()函数:
目录:\frameworks\base\core\java\android\provider\
说明:这个函数根据接收者列表和未保存的消息返回一个线程ID,如果这个消息开始一个
新的线程,那么函
数分配一个线程ID,否则返回一个适当的已经存在的线程ID;
2 ) MmsMessageSender.java sendMessage()函数:
目录:\packages\apps\mms\src\com\android\mms\transaction\
说明:对Mms进行封包
3 ) 再一次调用第一步函数
4 ) ConnectivityService.java startUsingNetworkFeature()函数:
目录:\framework\base\services\java\com\android\server\
说明:该函数为实现Mms 网络连接的关键函数,下面我们详细分析:
A、enforceChangePermission():判断调用的进程是否具有操作权限,如果不具
有,抛出一个
SecurityException异常,并强制准许权限
B 、 ConnectivityManager.isNetworkTypeValid(networkType)来判断
networkType是否合法,如果不合
法返回一个APN_REQUEST_FAILED,
在这里用到了最重要的ConnectivityManager类:
public class ConnectivityManager定义
在\frameworks\base\core\java\android\net的
ConnectivityManager.java里,其主要作用为:
1、监视网络连接,如WIFI、GPRS、UMTS等
2、当网路连接出现变化的时候,发送广播intents
3、当一个网络连接丢失之后,尝试连接另一个网络
4、为App提供粗粒度、细粒度的有效网络状态查询
C 、 FeatureUser f = new FeatureUser(networkType, feature, binder);
新建一个FeatureUser类变量,该类实现:当调用进程died时发送一个Notice,这样
就可以自我老化
D、int usedNetworkType = networkType;
if(networkType == ConnectivityManager.TYPE_MOBILE) {
if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI))
{
usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
}
}
这段代码获取使用的网络类型;
E、NetworkStateTracker network =
mNetTrackers[usedNetworkType];
NetworkStateTracker类在NetworkStateTracker.java里:每个子类保持跟踪
一个网络接口的连接状态,一
个网络的状态信息由一个Tracker类保持,基类管理networktypeindependent
网络状态
F、mFeatureUsers.add(f);
列表操作,将f添加到列表的end
G、if (!mNetRequestersPids[usedNetworkType].contains(currentPid))
{
// this gets used for perpid
dns when connected
mNetRequestersPids[usedNetworkType].add(currentPid);
}
判断网络操作需要的Pid是否包含当前Pid,如果不包含就添加进去
H、mHandler.sendMessageDelayed(mHandler.obtainMessage(
NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK,
f), getRestoreDefaultNetworkDelay());
消息发送,问题:消息的Handle函数也在该文件本地,?
I 、 if ((ni.isConnectedOrConnecting() == true) &&
!network.isTeardownRequested()) {
if (ni.isConnected() == true) {
// add the pidspecific
dns
Log.d(TAG, "fanyl test ++++ before
handleDnsConfigurationChange");
handleDnsConfigurationChange();
if (DBG) Log.d(TAG, "special network already active");
return Phone.APN_ALREADY_ACTIVE;
}
if (DBG) Log.d(TAG, "special network already connecting");
return Phone.APN_REQUEST_STARTED;
}
这里判断网络是正在连接还是已经连接完成,如果是已经连接完成,就去设置Dns,并返
回already状态
J、network.reconnect()
如果网络不是已经连接完成的状态的话,这里触发一个重新连接,直到网络状态变成
isConnected;
5 ) 接下来的操作存在于DataConnectionTracker.java里:
public synchronized int enableApnType(String type):
该函数确保用指定的类型连接APN,成功返回APN_ALREADY_ACTIVE或者
APN_REQUEST_STARTED
private void setEnabled(int id, boolean enable):
发送EVENT_ENABLE_NEW_APN事件
protected synchronized void onEnableApn(int apnId, int enabled)
该实例主要功能是判断目前是enable还是disable APN,如果是enable的话,调用
onEnableNewApn();
实现enable APN,如果是disable的话,根据
enabledCount,onCleanUpConnection关闭APN或者改为
默认连接
6 ) public void handleMessage(Message
msg),ConnectivityService.java里
进入到对事件EVENT_STATE_CHANGED的处理,state= CONNECTED, old=
CONNECTING,
reason= apnChanged, apnTypeList= mms,应该是最后调用了
handleConnect(info);发送一个广播事件
7 ) MobileDataStateTracker.java:MobileDataStateReceiver类的
public void onReceive(Context
context, Intent intent)函数里处理case CONNECTED处理;调用
setDetailedState(NetworkStateTracker类实例)发送了
EVENT_STATE_CHANGED事件
8 ) 然后又跳回ConnectivityService.java里的handleMessage函数
EVENT_STATE_CHANGED事件
的CONNECTED状态处理
9 ) handleConnect里最后调用updateNetworkSettings(实现在
NetworkStateTracker类里),并发送
sendConnectedBroadcast(info);广播事件
10 ) ConnectivityService.java handleDnsConfigurationChange配置
DNS信息,并在
handleConnect 函数调用addPrivateDnsRoutes添加路由信息
11 ) 接下来调用了GpsLocationProvider.java里的updateNetworkState和
runLocked,原因不
明?
12 ) 接下来返回去调用
startUsingNetworkFeature(ConnectivityService.java),又一次add
dns ?,然后返回APN_ALREADY_ACTIVE状态
13 ) ensureRouteToHost()
(/packages/apps/Mms/src/com/android/mms/transaction/Transaction
.java)调用了ConnectivityManager
类里的requestRouteToHost
至此关于Mms的Apn网络连接就建立起来了,
下面的步骤是在RILJ层以及RILD层实现数据和AT命令与modem的数据通信,省去
下面分析disable APN的流程,基本上就是Start的反过程:
1 ) 数据通信完毕,SendTransaction.java里的run函数给出数据通信完成之后的状
态
2 ) stopUsingNetworkFeature()(ConnectivityService实例);这个就是
startUsingNetworkFeature
的反过程
3 ) disableApnType(mms),DataConnectionTracker类
4 ) setEnabled,DataConnectionTracker类
2. Android 彩信发送介绍
这篇写彩信发送过程。
我想追踪的内容是:用户按下发送之后,彩信的图片阿数据阿文件阿,是怎么包装起来,最后发送出去。
按我看源码的先后顺序来写了。 写完可能最后整理下。
1. com.Android.mms.data.WorkingMessage.Java 类
send()函数。 注释如下:
/**
* Send this message over the network. Will call back with onMessageSent()
* once it has been dispatched to the telephony stack. This WorkingMessage
* object is no longer useful after this method has been called.
*/
这个是 2.1 的源码
Java 代码
复制到剪贴板 Java 代码
public void send() {
if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
LogTag.debug("send");
}
// Get ready to write to disk.
prepareForSave(true /* notify */);
// We need the recipient list for both SMS and MMS.
final Conversation conv = mConversation;
String msgTxt = mText.toString();
if (requiresMms() || addressContainsEmailToMms(conv, msgTxt)) {
// Make local copies of the bits we need for sending a message,
// because we will be doing it off of the main thread, which will
// immediately continue on to resetting some of this state.
final Uri mmsUri = mMessageUri;
final PduPersister persister = PduPersister
.getPduPersister(mContext);
final SlideshowModel slideshow = mSlideshow;
final SendReq sendReq = makeSendReq(conv, mSubject);
// Make sure the text in slide 0 is no longer holding onto a
// reference to the text
// in the message text box.
slideshow.prepareForSend();
// Do the dirty work of sending the message off of the main UI
// thread.
new Thread(new Runnable() {
public void run() {
sendMmsWorker(conv, mmsUri, persister, slideshow, sendReq);
}
}).start();
} else {
// Same rules apply as above.
final String msgText = mText.toString();
new Thread(new Runnable() {
public void run() {
sendSmsWorker(conv, msgText);
}
}).start();
}
// update the Recipient cache with the new to address, if it's different
RecipientIdCache
.updateNumbers(conv.getThreadId(), conv.getRecipients());
// Mark the message as discarded because it is "off the market" after
// being sent.
mDiscarded = true;
}
粗浅的解说一下,
(1) prapareForSave. 先确保有 slidshow,也就是实质内容。 确保文字已拷贝。确保标题。
(2a) 根据消息分类,如果是短信直接起一个线程,跑 sendSmsWorker 函数,发送短信
(2b) 如果是彩信,先跑这么个函数,确保文本信息
// Make sure the text in slide 0 is no longer holding onto a // reference to the
text // in the message text box.
slideshow.prepareForSend();
TheCranberriers(卡百利)的歌真好听。
然后起一个线程,单独跑 sendMmsWorker 函数,后文有介绍。
彩信比 sms 麻烦很多。从 sendMmsWorker 函数的参数就可以看出来:(conv, mmsUri, persister,
slideshow, sendReq) 上下文,uri,PduPersister(彩信是用 pdu 的),slideshow 包含了所有的彩
信信息,sendreq 包含了 mime 封装 mms 时的 headers(在我的剥壳彩信 2 里面有提到)。包括了
ContentType("application/vnd.wap.multipart.related" ,from,to 等信息 。
(3)。 不管是短信还是彩信,起了那俩个 worker 函数之一就算发送信息成功了。
最后修改 Recipient cache, 重置标志位,过程就结束了。
2。函数 sendMmsWorker
Java 代码
复制到剪贴板 Java 代码
private void sendMmsWorker(Conversation conv, Uri mmsUri,
PduPersister persister, SlideshowModel slideshow, SendReq sendReq) {
// First make sure we don't have too many outstanding unsent message.
Cursor cursor = null;
try {
Log.d("GN@@@","mContext: "+mContext.toString());
Log.d("GN@@@","mContentResolver: "+mContentResolver.toString());
Log.d("GN@@@","Mms.Outbox.CONTENT_URI: "+Mms.Outbox.CONTENT_URI.toString
());
cursor = SqliteWrapper.query(mContext, mContentResolver,
Mms.Outbox.CONTENT_URI, MMS_OUTBOX_PROJECTION, null, null,
null);
if (cursor != null) {
long maxMessageSize = MmsConfig
.getMaxSizeScaleForPendingMmsAllowed()
* MmsConfig.getMaxMessageSize();
long totalPendingSize = 0;
while (cursor.moveToNext()) {
totalPendingSize += cursor.getLong(MMS_MESSAGE_SIZE_INDEX);
}
if (totalPendingSize >= maxMessageSize) {
unDiscard(); // it wasn't successfully sent. Allow it to be
// saved as a draft.
mStatusListener.onMaxPendingMessagesReached();
return;
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
mStatusListener.onPreMessageSent();
// Make sure we are still using the correct thread ID for our
// recipient set.
long threadId = conv.ensureThreadId();
if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
LogTag.debug("sendMmsWorker: update draft MMS message " + mmsUri);
}
if (mmsUri == null) {
// Create a new MMS message if one hasn't been made yet.
mmsUri = createDraftMmsMessage(persister, sendReq, slideshow);
} else {
// Otherwise, sync the MMS message in progress to disk.
updateDraftMmsMessage(mmsUri, persister, slideshow, sendReq);
}
// Be paranoid and clean any draft SMS up.
deleteDraftSmsMessage(threadId);
MessageSender sender = new MmsMessageSender(mContext, mmsUri, slideshow
.getCurrentMessageSize());
try {
if (!sender.sendMessage(threadId)) {
// The message was sent through SMS protocol, we should
// delete the copy which was previously saved in MMS drafts.
SqliteWrapper.delete(mContext, mContentResolver, mmsUri, null,
null);
}
// Make sure this thread isn't over the limits in message count
Recycler.getMmsRecycler().deleteOldMessagesByThreadId(mContext,
threadId);
} catch (Exception e) {
Log.e(TAG, "Failed to send message: " + mmsUri + ", threadId="
+ threadId, e);
}
mStatusListener.onMessageSent();
}
依旧是粗浅的解说:
a )前面挺长一段代码,检查这个对话(conversation)之前还有没有未发送的信息,uri 是
Mms.Outbox.CONTENT_URI。
这里需要提到一下 MessageStatusListener,这个 Interface 接口实现在 WorkingMessage.java 里,
而短信类的主题 ComposeMessageActivity.java 实现了这个接口,所以前者在一些状态改变的时候可
以很方便的调用后者的一些函数,作相应的改动。主要是:onProtocolChanged 彩信短信互切换,
onAttachmentChanged 福建改变,onPreMessageSent 发消息前,onMessageSent 发消息后。
b)
当然,这里调用了 onPreMessageSent 这个监听函数,
然后 ComposeMessageActivity 就会调用 resetMessage 函数 ,这个函数会调整显示,focus,软键
盘等等。
c)
然后检查 mmsUri。如果这个 uri 是空的话,直接造一个新的 uri 继续发送。这个真是让我叫亚灭爹。因
为一开始不知道
这个 createDraftMmsMessage(persister, sendReq, slideshow);函数可以包含所有发送需要的信息,
以为这么发出去太可怕了。
如果 uri 不为空。 调用的是 updateDraftMmsMessage(mmsUri, persister, slideshow, sendReq);
总之功能是,把这个将发送的 mms,存 disk 了,也就是存 draft 了。为什么要发送还要存 draft 呢,后
面另会说,因为这个是我写这个文章前想要找的东西。。。这个过程还有一些信息写道 mmsUri 了。所以
之后 mmsUri 就可以代表将发送的 mms 的全部信息。
d)deleteDraftSmsMessage 删除草稿
e)创建一个 MmsMessageSender,用这个 sender 来调用 sendMessage 函数
可以猜到的,Sms 那边是 SmsMessageSender,同样调用 sendMessage 函数
通过这里之后,短信已经真的发掉了。 这个类后面有介绍。
f)这里这个 if 相当搞笑,按正常流程下来,按理这里本来这里是一个彩信的发送,然后有一些数据在
draft 数据库,会在上面的流程中被移到 send 数据库。
但是搞笑的地方来了:因为忽然发现函数返回值表示刚刚发送出去的其实是一个短信 sms,而已。于是
要把数据库里存着的 draft 删掉。
我也不知道这个 if 里面的情况会不会发生,反正源码是这么写的,我只管不负责任直译。。。
g)调用 onMessageSent 这个监听函数。调用 ComposeMessageActivity 的 onMessageSent,这个
函数功能是重新显示 conversation list。
3 MmsMessageSender.java 类。在 mms/transaction 下面。实现了 MessageSender 接口。这个接
口只有一个事儿,就是 sendMessage 并返回 boolean 的值。弱发送的是 mms,返回 true。若发送的
是 sms,返回 false。出错返回啥?exception。
我最先想要追踪的发送流程也在这里了。贴一些代码
Java 代码
复制到剪贴板 Java 代码
public MmsMessageSender(Context context, Uri location, long messageSize) {
mContext = context;
mMessageUri = location;
mMessageSize = messageSize;
if (mMessageUri == null) {
throw new IllegalArgumentException("Null message URI.");
}
}
Java 代码
public boolean sendMessage(long token) throws MmsException {
// Load the MMS from the message uri
PduPersister p = PduPersister.getPduPersister(mContext);
GenericPdu pdu = p.load(mMessageUri);
if (pdu.getMessageType() != PduHeaders.MESSAGE_TYPE_SEND_REQ) {
throw new MmsException("Invalid message: " + pdu.getMessageType());
}
SendReq sendReq = (SendReq) pdu;
// Update headers.
updatePreferencesHeaders(sendReq);
// MessageClass.
sendReq.setMessageClass(DEFAULT_MESSAGE_CLASS.getBytes());
// Update the 'date' field of the message before sending it.
sendReq.setDate(System.currentTimeMillis() / 1000L);
sendReq.setMessageSize(mMessageSize);
p.updateHeaders(mMessageUri, sendReq);
// Move the message into MMS Outbox
p.move(mMessageUri, Mms.Outbox.CONTENT_URI);
// Start MMS transaction service
SendingProgressTokenManager
.put(ContentUris.parseId(mMessageUri), token);
mContext.startService(new Intent(mContext, TransactionService.class));
return true;
}
解说:
现从 PduPersister 那里拿数据,包括需要拼装的发送报头和需要发送的数据信息。
然后把要发送的信息相关数据从数据库的 draft 那里转移到 send,表示已经发送。
最后起一个 TransactionService 服务,这个服务也是从 PduPersister 里找,找到需要发送的数据,并
通过不同的用户网络送出去。
这块我猜一人都没有改的需求。。
4.
createDraftMmsMessage(persister, sendReq, slideshow); 和
updateDraftMmsMessage(mmsUri, persister, slideshow, sendReq); 这两个函数
刨掉 try catch , createDraftMmsMessage 函数大概有这么几句:
复制到剪贴板 Java 代码
Java 代码
PduBody pb = slideshow.toPduBody();
sendReq.setBody(pb);
Uri res = persister.persist(sendReq, Mms.Draft.CONTENT_URI);
slideshow.sync(pb);
updateDraftMmsMessage 函数大概有这么几句:
Java 代码
persister.updateHeaders(uri, sendReq);
final PduBody pb = slideshow.toPduBody();
persister.updateParts(uri, pb);
slideshow.sync(pb);
两个函数从本质上讲是一样的:把附件的东西以 pdubody 的形式存下来,另外就是更新 uri。
什么叫 PduBody 呢? 厉害了。就是 n 个 PduPart。什么叫 PduPart 呢?厉害了,就是数据库里的那个
Part!那个 part 是什么?
那个最厉害了。数据库里的 PART_1234455 这种数据,文件名代表创建时间(在 mediaModel 产生时
就进系统了),导出来就是源文件,比如图片文件,改个 jpg 就可以看了。
sync 函数不怎么动,无责任解说:把每个 slide 里面每个媒体跟真实文件位置对应上。
slideshow.toPduBody();里面,用 SMILDocument mDocumentCache;
调用到 SlideshowModel.java 的
Java 代码
复制到剪贴板 Java 代码
//其中 context=null。 isMakingCopy=false。 document=mDocumentCache
private PduBody makePduBody(Context context, SMILDocument document,
boolean isMakingCopy) {
PduBody pb = new PduBody();
boolean hasForwardLock = false;
for (SlideModel slide : mSlides) {
for (MediaModel media : slide) {
if (isMakingCopy) {
if (media.isDrmProtected() && !media.isAllowedToForward()) {
hasForwardLock = true;
continue;
}
}
PduPart part = new PduPart();
if (media.isText()) {
TextModel text = (TextModel) media;
// Don't create empty text part.
if (TextUtils.isEmpty(text.getText())) {
continue;
}
// Set Charset if it's a text media.
part.setCharset(text.getCharset());
}
// Set Content-Type.
part.setContentType(media.getContentType().getBytes());
String src = media.getSrc();
String location;
boolean startWithContentId = src.startsWith("cid:");
if (startWithContentId) {
location = src.substring("cid:".length());
} else {
location = src;
}
// Set Content-Location.
part.setContentLocation(location.getBytes());
// Set Content-Id.
if (startWithContentId) {
// Keep the original Content-Id.
part.setContentId(location.getBytes());
} else {
int index = location.lastIndexOf(".");
String contentId = (index == -1) ? location : location
.substring(0, index);
part.setContentId(contentId.getBytes());
}
if (media.isDrmProtected()) {
DrmWrapper wrapper = media.getDrmObject();
part.setDataUri(wrapper.getOriginalUri());
part.setData(wrapper.getOriginalData());
} else if (media.isText()) {
part.setData(((TextModel) media).getText().getBytes());
} else if (media.isImage() || media.isVideo()
|| media.isAudio()) {
part.setDataUri(media.getUri());
} else {
Log.w(TAG, "Unsupport media: " + media);
}
pb.addPart(part);
}
}
if (hasForwardLock && isMakingCopy && context != null) {
Toast.makeText(context,
context.getString(R.string.cannot_forward_drm_obj),
Toast.LENGTH_LONG).show();
document = SmilHelper.getDocument(pb);
}
// Create and insert SMIL part(as the first part) into the PduBody.
ByteArrayOutputStream out = new ByteArrayOutputStream();
SmilXmlSerializer.serialize(document, out);
PduPart smilPart = new PduPart();
smilPart.setContentId("smil".getBytes());
smilPart.setContentLocation("smil.xml".getBytes());
smilPart.setContentType(ContentType.APP_SMIL.getBytes());
smilPart.setData(out.toByteArray());
pb.addPart(0, smilPart);
return pb;
}
好了,齐活儿了