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里,其主要做用为: 一、监视网络链接,如WIFI、GPRS、UMTS等 二、当网路链接出现变化的时候,发送广播intents 三、当一个网络链接丢失以后,尝试链接另外一个网络 四、为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; } 好了,齐活儿了