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; } 好了,齊活兒了