天天看點

Android Mms短信的發送流程,短信發送源碼解析

Android Mms短信的發送流程,短信發送源碼解析
  • 發送前的校驗

從短信的點選按鈕開始着手:

// packages/apps/Mms/src/com/android/mms/ui/ComposeMessageActivity.java
    @Override
    public void onClick(View v) {
        mIsRTL = (v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
        if ((v == mSendButtonSms || v == mSendButtonMms) && isPreparedForSending()) {
            ... ...
            if (mShowTwoButtons) {
                confirmSendMessageIfNeeded(SubscriptionManager.getSubId(PhoneConstants.SUB1)[]);
            } else {
                if (mIsRcsEnabled) {
                    if (RcsUtils.isRcsOnline()
                            && ((mWorkingMessage.getCacheRcsMessage() || (!mWorkingMessage
                                    .requiresMms() && mWorkingMessage.hasText())))) {
                        rcsSend();
                    } else {
                        if (mConversation.isGroupChat()) {
                            toast(R.string.rcs_offline_unable_to_send);
                            return;
                        }
                        if (mIsBurnMessage) {
                            toast(R.string.rcs_not_online_can_not_send_burn_message);
                            mIsBurnMessage = false;
                            return;
                        } else {
                            confirmSendMessageIfNeeded();
                        }
                    }
                } else {
                    confirmSendMessageIfNeeded();
                }
            }
        } 
        ... ...
    }
           

首先判斷是否是雙卡的情況,雙卡的情況下通過SubId來區分哪張卡;單卡則判斷是否啟用RCS(rcs預設是關閉的,配置開關的路徑:/Mms/res/values/config.xml config_rcs_sms_version)沒有啟用會執行confirmSendMessageIfNeeded對收件人位址資訊确認.

private void confirmSendMessageIfNeeded() {
        LogTag.debugD("confirmSendMessageIfNeeded");
        if (mRcsShareVcard) {
            mWorkingMessage.setRcsType(RcsUtils.RCS_MSG_TYPE_VCARD);
            mRcsShareVcard = false;
        }

        int slot = SubscriptionManager.getSlotId(
                SmsManager.getDefault().getDefaultSmsSubscriptionId());
        if ((TelephonyManager.getDefault().isMultiSimEnabled() &&
                isLTEOnlyMode(slot))
                || (!TelephonyManager.getDefault().isMultiSimEnabled()
                        && isLTEOnlyMode())) {
            showDisableLTEOnlyDialog(slot);
            LogTag.debugD("return for disable LTEOnly");
            return;
        }

        boolean isMms = mWorkingMessage.requiresMms();
        int defaultSubId = SubscriptionManager.getDefaultSmsSubscriptionId();
        if (!isRecipientsEditorVisible()) {
            if (TelephonyManager.getDefault().isMultiSimEnabled()) {
                if ((TelephonyManager.getDefault().getPhoneCount()) >  &&
                        MessageUtils.isMsimIccCardActive()) {
                    if(SmsManager.getDefault().isSMSPromptEnabled()) {
                        launchMsimDialog(true, isMms);
                    } else {
                        sendMsimMessageNotPrompt(true, isMms, defaultSubId);
                    }
                } else {
                    sendMsimMessageNotPrompt(true, isMms, defaultSubId);
                }
            } else if (isMmsWithMobileDataOff(isMms, defaultSubId)) {
                showMobileDataDisabledDialog();
            } else {
                sendMessage(true);
            }
            return;
        }
        // 判斷收件人位址是否有效
        if (mRecipientsEditor.hasInvalidRecipient(isMms)) {
            showInvalidRecipientDialog();
        } else if (TelephonyManager.getDefault().isMultiSimEnabled()) { // 判斷是否是雙卡的情況
            if ((TelephonyManager.getDefault().getPhoneCount()) >  &&
                    MessageUtils.isMsimIccCardActive()) {
                if(SmsManager.getDefault().isSMSPromptEnabled()) {
                    launchMsimDialog(true, isMms);
                } else {
                    sendMsimMessageNotPrompt(true, isMms, defaultSubId);
                }
            } else {
                sendMsimMessageNotPrompt(true, isMms, defaultSubId);
            }
        } else if (isMmsWithMobileDataOff(isMms, defaultSubId)) {   // 判斷是否是彩信,并且資料流量是否打開
            showMobileDataDisabledDialog(defaultSubId);
        } else {
            if (!TextUtils.isEmpty(getString(R.string.mms_recipient_Limit))
                    && isMms
                    && checkForMmsRecipients(getString(R.string.mms_recipient_Limit), true)) {
                return;
            }
            //擷取收件人位址,發送
            // The recipients editor is still open. Make sure we use what's showing there
            // as the destination.
            ContactList contacts = mRecipientsEditor.constructContactsFromInput(false);
            mDebugRecipients = contacts.serialize();
            sendMessage(true);
        }
    }
           

這裡是對收件人位址檢測等一些狀态判斷,最後通過sendMessage()繼續發送邏輯.

private void sendMessage(boolean bCheckEcmMode) {
        LogTag.debugD("sendMessage true");
        if (mIsRcsEnabled && hasConvertRcsAttachmentToMmsAndSent()) {
            return;
        }
        // Check message size, if >= max message size, do not send message.
        if(checkMessageSizeExceeded()){
            LogTag.debugD("MessageSizeExceeded");
            return;
        }
        // 檢查目前是否是ECM模式
        if (bCheckEcmMode) {
            // TODO: expose this in telephony layer for SDK build
            String inEcm = SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE);
            if (Boolean.parseBoolean(inEcm)) {
                try {
                    startActivityForResult(
                            new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null),
                            REQUEST_CODE_ECM_EXIT_DIALOG);
                    return;
                } catch (ActivityNotFoundException e) {
                    // continue to send message
                    Log.e(TAG, "Cannot find EmergencyCallbackModeExitDialog", e);
                }
            }
        }

        // Make the recipients editor lost focus, recipients editor will shrink
        // and filter useless char in recipients to avoid send sms failed.
        if (isRecipientsEditorVisible()
                && mRecipientsEditor.isFocused()
                && !mWorkingMessage.requiresMms()) {
            mTextEditor.requestFocus();
        }
        // 判斷是否有短信正在發送
        if (!mSendingMessage) {
            if (LogTag.SEVERE_WARNING) {
                String sendingRecipients = mConversation.getRecipients().serialize();
                if (!sendingRecipients.equals(mDebugRecipients)) {
                    String workingRecipients = mWorkingMessage.getWorkingRecipients();
                    if (workingRecipients != null && !mDebugRecipients.equals(workingRecipients)) {
                        LogTag.warnPossibleRecipientMismatch("ComposeMessageActivity.sendMessage" +
                                " recipients in window: \"" +
                                mDebugRecipients + "\" differ from recipients from conv: \"" +
                                sendingRecipients + "\" and working recipients: " +
                                workingRecipients, this);
                    }
                }
                sanityCheckConversation();
            }
            // send can change the recipients. Make sure we remove the listeners first and then add
            // them back once the recipient list has settled.
            removeRecipientsListeners();

            // 判斷是否為多個聯系人,然後進入WorkingMessage執行發送
            if (mWorkingMessage.getResendMultiRecipients()) {
                // If resend sms recipient is more than one, use mResendSmsRecipient
                LogTag.debugD("mWorkingMessage send mResendSmsRecipient=" + mResendSmsRecipient);
                mWorkingMessage.send(mResendSmsRecipient);
            } else {
                LogTag.debugD("mWorkingMessage send mDebugRecipients=" + mDebugRecipients);
                mWorkingMessage.send(mDebugRecipients);
            }

            mSentMessage = true;
            mSendingMessage = true;
            addRecipientsListeners();

            mScrollOnSend = true;   // in the next onQueryComplete, scroll the list to the end.
        }
        // But bail out if we are supposed to exit after the message is sent.
        if (mSendDiscreetMode || MessageUtils.isMailboxMode()) {
            finish();
        }
    }
           

WorkingMessage包含所有使用者編輯短信相關的狀态,這裡判斷如果沒有短信正在發送,則通過WorkingMessage發送短信.

/**
     * 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.
     *
     * @throws ContentRestrictionException if sending an MMS and uaProfUrl is not defined
     * in mms_config.xml.
     */
    public void send(final String recipientsInUI) {
        long origThreadId = mConversation.getThreadId();

        LogTag.debugD("send origThreadId: " + origThreadId);

        removeSubjectIfEmpty(true /* notify */);

        // Get ready to write to disk.
        prepareForSave(true /* notify */);

        // Make sure the mConversation has Recipients
        checkConversationHasRecipients(recipientsInUI);

        // We need the recipient list for both SMS and MMS.
        final Conversation conv = mConversation;
        String msgTxt = mText.toString();
        if (MmsConfig.isRcsEnabled() && shouldSendMessageWithRcsPolicy()) {
            sendRcsMessage(recipientsInUI);
            return;
        }
        if (requiresMms() || addressContainsEmailToMms(conv, msgTxt)) {     // 發送彩信
            // uaProfUrl setting in mms_config.xml must be present to send an MMS.
            // However, SMS service will still work in the absence of a uaProfUrl address.
            if (MmsConfig.getUaProfUrl() == null) {
                String err = "WorkingMessage.send MMS sending failure. mms_config.xml is " +
                        "missing uaProfUrl setting.  uaProfUrl is required for MMS service, " +
                        "but can be absent for SMS.";
                RuntimeException ex = new NullPointerException(err);
                Log.e(TAG, err, ex);
                // now, let's just crash.
                throw ex;
            }

            // 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(mActivity);

            final SlideshowModel slideshow = mSlideshow;
            final CharSequence subject = mSubject;
            final boolean textOnly = mAttachmentType == TEXT;

            LogTag.debugD("Send mmsUri: " + mmsUri);
            // 彩信發送線程 
            // Do the dirty work of sending the message off of the main UI thread.
            new Thread(new Runnable() {
                @Override
                public void run() {
                    final SendReq sendReq = makeSendReq(conv, subject);

                    // Make sure the text in slide 0 is no longer holding onto a reference to
                    // the text in the message text box.
                    slideshow.prepareForSend();
                    sendMmsWorker(conv, mmsUri, persister, slideshow, sendReq, textOnly);

                    updateSendStats(conv);
                }
            }, "WorkingMessage.send MMS").start();
        } else {        //發送短信
            // Same rules apply as above.
            // Add user's signature first if this feature is enabled.
            String text = mText.toString();
            LogTag.debugD("mText="+text);
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
            if (sp.getBoolean("pref_key_enable_signature", false)) {
                String signature = (sp.getString("pref_key_edit_signature", "")).trim();
                if (signature.length() > ) {
                    String sigBlock = "\n" + signature;
                    if (!text.endsWith(sigBlock)) {
                        // Signature should be written behind the text in a
                        // newline while the signature has changed.
                        text += sigBlock;
                    }
                }
            }
            final String msgText = text;
            // 短信發送線程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    preSendSmsWorker(conv, msgText, recipientsInUI);

                    updateSendStats(conv);
                }
            }, "WorkingMessage.send SMS").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;
    }
           

通過網絡發送消息,分别為短信和彩信開啟了發送線程

// Message sending stuff
    private void preSendSmsWorker(Conversation conv, String msgText, String recipientsInUI) {
        mIsSending = true;
        // If user tries to send the message, it's a signal the inputted text is what they wanted.
        UserHappinessSignals.userAcceptedImeText(mActivity);
        // 短信準備發送的回調(這裡執行了UI的更新)
        mStatusListener.onPreMessageSent();
        
        long origThreadId = conv.getThreadId();
        // 擷取線程ID(每個收件人對應一個線程ID,threadId也是指一個會話的ID,和sms和thread資料庫中thread_id和_id對應)
        // Make sure we are still using the correct thread ID for our recipient set.
        long threadId = conv.ensureThreadId();

        String semiSepRecipients = conv.getRecipients().serialize();

        // recipientsInUI can be empty when the user types in a number and hits send
        if (LogTag.SEVERE_WARNING && ((origThreadId !=  && origThreadId != threadId) ||
                ((!mResendMultiRecipients && !semiSepRecipients.equals(recipientsInUI)) &&
                        !TextUtils.isEmpty(recipientsInUI)))) {
            String msg = origThreadId !=  && origThreadId != threadId ?
                    "WorkingMessage.preSendSmsWorker threadId changed or " +
                    "recipients changed. origThreadId: " +
                    origThreadId + " new threadId: " + threadId +
                    " also mConversation.getThreadId(): " +
                    mConversation.getThreadId()
                :
                    "Recipients in window: \"" +
                    recipientsInUI + "\" differ from recipients from conv: \"" +
                    semiSepRecipients + "\"";

            // Just interrupt the process of sending message if recipient mismatch
            LogTag.warnPossibleRecipientMismatch(msg, mActivity);
        } else {    // 發送短信
            // just do a regular send. We're already on a non-ui thread so no need to fire
            // off another thread to do this work.
            if (mResendMultiRecipients) {
                sendSmsWorker(msgText, recipientsInUI, threadId);
                mResendMultiRecipients = false;
            } else {
                sendSmsWorker(msgText, semiSepRecipients, threadId);
            }
            // 删除短信草稿
            // Be paranoid and clean any draft SMS up.
            deleteDraftSmsMessage(threadId);
        }
        mIsSending = false;
    }
           
  • 拆分多個聯系人執行發送
private void sendSmsWorker(String msgText, String semiSepRecipients, long threadId) {
        // 切割收件人位址
        String[] dests = TextUtils.split(semiSepRecipients, ";");
        LogTag.debugD("sendSmsWorker sending message: recipients=" +
                    semiSepRecipients + ", threadId=" + threadId);
        // 建構MessageSender對象發送短信
        MessageSender sender = new SmsMessageSender(mActivity, dests, msgText, threadId,
                mCurrentConvSubId);
        try {
            // 拆分聯系人,儲存到資料庫
            sender.sendMessage(threadId);

            // Make sure this thread isn't over the limits in message count
            Recycler.getSmsRecycler().deleteOldMessagesByThreadId(mActivity, threadId);
        } catch (Exception e) {
            Log.e(TAG, "Failed to send SMS message, threadId=" + threadId, e);
        }
        // 短信發送後的回調UI接口(此時短信已被寫入資料庫,重新整理UI資訊清單)
        mStatusListener.onMessageSent();
        MmsWidgetProvider.notifyDatasetChanged(mActivity);
    }
           

建立MessageSender對象發送短信,同時在發送前後都回調發送狀态的監聽.SmsMessageSender實作MessageSender接口,該接口有三個實作類分别是:

MmsMessageSender: 彩信的發送
SmsSingleRecipientSender: 單個聯系人的短信發送
SmsMessageSender: 拆分多個聯系人,最終通過SmsSingleRecipientSender發送
           
public boolean sendMessage(long token) throws MmsException {
        // In order to send the message one by one, instead of sending now, the message will split,
        // and be put into the queue along with each destinations
        return queueMessage(token);
    }

    private boolean queueMessage(long token) throws MmsException {
        if ((mMessageText == null) || (mNumberOfDests == )) {
            // Don't try to send an empty message.
            if (!(mMessageText == null &&
                    mContext.getResources().getBoolean(R.bool.enable_send_blank_message))) {
                throw new MmsException("Null message body or dest.");
            }
        }

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        boolean requestDeliveryReport = false;
        if (TelephonyManager.getDefault().isMultiSimEnabled()) {
            int slotId = SubscriptionManager.getSlotId(mSubId);
            if (MessageUtils.isMsimIccCardActive()) {
                requestDeliveryReport = prefs.getBoolean((slotId == PhoneConstants.SUB1) ?
                        MessagingReportsPreferenceActivity.SMS_DELIVERY_REPORT_SUB1 :
                        MessagingReportsPreferenceActivity.SMS_DELIVERY_REPORT_SUB2,
                        DEFAULT_DELIVERY_REPORT_MODE);
            } else {
                requestDeliveryReport = prefs.getBoolean((slotId == PhoneConstants.SUB1) ?
                        SmsPreferenceActivity.SMS_DELIVERY_REPORT_SIM1 :
                        SmsPreferenceActivity.SMS_DELIVERY_REPORT_SIM2,
                        DEFAULT_DELIVERY_REPORT_MODE);
            }
        } else {
            requestDeliveryReport = prefs.getBoolean(
                    SmsPreferenceActivity.SMS_DELIVERY_REPORT_NO_MULTI,
                    DEFAULT_DELIVERY_REPORT_MODE);
        }

        int priority = -;
        try {
            String priorityStr = PreferenceManager.getDefaultSharedPreferences(mContext).getString(
                    "pref_key_sms_cdma_priority", "");
            priority = Integer.parseInt(priorityStr);
        } catch (Exception e) {
            Log.w(TAG, "get priority error:" + e);
        }
        // 周遊收件人,加入發送隊列
        for (int i = ; i < mNumberOfDests; i++) {
            try {
                LogTag.debugD("queueMessage mDests[i]: " + mDests[i] + " mThreadId: " + mThreadId);
                // Check to see whether short message count is up to 2000 for cmcc
                if (MessageUtils.checkIsPhoneMessageFull(mContext)) {
                    break;
                }
                log("updating Database with subId = " + mSubId);
                // 加入發送隊列
                Sms.addMessageToUri(mSubId, mContext.getContentResolver(),
                        Uri.parse("content://sms/queued"), mDests[i],
                        mMessageText, null, mTimestamp,
                        true /* read */,
                        requestDeliveryReport,
                        mThreadId, priority);
                MessageUtils.updateThreadAttachTypeByThreadId(mContext, mThreadId);
            } catch (SQLiteException e) {
                if (LogTag.DEBUG_SEND) {
                    Log.e(TAG, "queueMessage SQLiteException", e);
                }
                SqliteWrapper.checkSQLiteException(mContext, e);
            }
        }
        // 通知SmsReceiver,在SmsReceiver中轉發帶有SmsReceiverService.ACTION_SEND_MESSAGE的intent.
        Intent intent = new Intent(SmsReceiverService.ACTION_SEND_MESSAGE, null, mContext,
                SmsReceiver.class);
        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId);
        // Notify the SmsReceiverService to send the message out
        mContext.sendBroadcast(intent);
        return false;
    }
           

這裡主要是分析收件人的個數,然後把資訊按每個收件人都放到發送隊列資料庫中,通過Sms.addMessageToUri( ... ... )儲存到content://sms/queued資料庫.然後喚起SmsReceiver來處理隊列.

SmsMessageSender的sendMessage()傳回後WorkingMessage會再次回調UI的接口(mStatusListener.onMessageSent())重新整理資訊清單.

// packages/apps/Mms/src/com/android/mms/transaction/SmsReceiver.java
    @Override
    public void onReceive(Context context, Intent intent) {
        onReceiveWithPrivilege(context, intent, false);
    }

    protected void onReceiveWithPrivilege(Context context, Intent intent, boolean privileged) {
        if (!MessageUtils.hasBasicPermissions()) {
            Log.d("Mms", "SmsReceiver do not have basic permissions");
            return;
        }
        String action = intent.getAction();
        LogTag.debugD("onReceiveWithPrivilege:intent="+intent+"|privileged="+privileged);
        // If 'privileged' is false, it means that the intent was delivered to the base
        // no-permissions receiver class.  If we get an SMS_RECEIVED message that way, it
        // means someone has tried to spoof the message by delivering it outside the normal
        // permission-checked route, so we just ignore it.
        if (!privileged && (Intents.SMS_DELIVER_ACTION.equals(action) ||
                "android.cellbroadcastreceiver.CB_AREA_INFO_RECEIVED".equals(action))) {
            return;
        }
        intent.setClass(context, SmsReceiverService.class);
        intent.putExtra("result", getResultCode());
        beginStartingService(context, intent);
    }
           

SmsReceiver主要負責監聽廣播,然後将消息轉發給SmsReceiverService處理.SmsReceiverService是處理短信收發工作線程,允許我們将傳入的消息存儲到資料庫、更新通知等,而不阻塞SmsReceiver所運作的主線程.

// packages/apps/Mms/src/com/android/mms/transaction/SmsReceiverService.java
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!MmsConfig.isSmsEnabled(this)) {
            LogTag.debugD("SmsReceiverService: is not the default sms app");
            // NOTE: We MUST not call stopSelf() directly, since we need to
            // make sure the wake lock acquired by AlertReceiver is released.
            SmsReceiver.finishStartingService(SmsReceiverService.this, startId);
            return Service.START_NOT_STICKY;
        }
        // Temporarily removed for this duplicate message track down.

        int resultCode = intent != null ? intent.getIntExtra("result", ) : ;

        if (resultCode != ) {
            LogTag.debugD("onStart: #" + startId + " resultCode: " + resultCode +
                    " = " + translateResultCode(resultCode));
        }
        // 處理intent中的資訊
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
        return Service.START_NOT_STICKY;
    }
           

該服務啟動後通過mServiceHandler處理intent中的傳來的請求

/**
         * Handle incoming transaction requests.
         * The incoming requests are initiated by the MMSC Server or by the MMS Client itself.
         */
        @Override
        public void handleMessage(Message msg) {
            int serviceId = msg.arg1;
            Intent intent = (Intent)msg.obj;
            LogTag.debugD("handleMessage serviceId: " + serviceId + " intent: " + intent);
            if (intent != null && MmsConfig.isSmsEnabled(getApplicationContext())) {
                String action = intent.getAction();

                int error = intent.getIntExtra("errorCode", );

                LogTag.debugD("handleMessage action: " + action + " error: " + error);

                if (MESSAGE_SENT_ACTION.equals(intent.getAction())) {
                    handleSmsSent(intent, error);
                } else if (SMS_DELIVER_ACTION.equals(action)) {
                    handleSmsReceived(intent, error);
                } else if (CB_AREA_INFO_RECEIVED_ACTION.equals(action)) {
                    handleCbSmsReceived(intent, error);
                } else if (ACTION_BOOT_COMPLETED.equals(action)) {
                    handleBootCompleted();
                } else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
                    handleServiceStateChanged(intent);
                } else if (ACTION_SEND_MESSAGE.endsWith(action)) {
                    // 發送短信
                    handleSendMessage(intent);
                } else if (ACTION_SEND_INACTIVE_MESSAGE.equals(action)) {
                    handleSendInactiveMessage();
                }
            }
            // NOTE: We MUST not call stopSelf() directly, since we need to
            // make sure the wake lock acquired by AlertReceiver is released.
            SmsReceiver.finishStartingService(SmsReceiverService.this, serviceId);
        }
           

SmsReceiver轉發的消息都由handleMessage來處理.之前發送廣播使用的是ACTION_SEND_MESSAGE,是以這裡響應處理handleSendMessage方法.

private void handleSendMessage(Intent intent) {
        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                SubscriptionManager.getDefaultSmsSubscriptionId());
        if (mSending.get(subId) == null) {
           mSending.put(subId, false);
        }
        // 判斷目前是否有發送任務
        if (!mSending.get(subId)) {
            sendFirstQueuedMessage(subId);
        } else {
            LogTag.debugD("subId=" + subId + " is in mSending ");
        }
    }
           
// Send first queued message of the given subscription
    public synchronized void sendFirstQueuedMessage(int subscription) {
        boolean success = true;
        boolean isExpectMore = false;
        // get all the queued messages from the database
        final Uri uri = Uri.parse("content://sms/queued");
        ContentResolver resolver = getContentResolver();
        String where = "sub_id=?";
        String[] whereArgs = new String[] {Integer.toString(subscription)};
        // 從隊列中取出短信
        Cursor c = SqliteWrapper.query(this, resolver, uri,
                        SEND_PROJECTION, where, whereArgs, "date ASC"); // date ASC so we send out
                                                                       // in same order the user
                                                                       // tried to send messages.
        if (c != null) {
            try {
                if (c.moveToFirst()) {      // 發送第一條短信
                    String msgText = c.getString(SEND_COLUMN_BODY);
                    String address = c.getString(SEND_COLUMN_ADDRESS);
                    int threadId = c.getInt(SEND_COLUMN_THREAD_ID);
                    int status = c.getInt(SEND_COLUMN_STATUS);

                    int msgId = c.getInt(SEND_COLUMN_ID);
                    int subId = c.getInt(SEND_COLUMN_SUB_ID);
                    int priority = c.getInt(SEND_COLUMN_PRIORITY);
                    Uri msgUri = ContentUris.withAppendedId(Sms.CONTENT_URI, msgId);
                    // Get the information of is there any messages are pending to process.
                    // If yes, send this inforamtion to framework to control the link and send all
                    // messages on same link based on the support in framework
                    if (c.moveToNext()) {
                        isExpectMore = true;
                    }
                    // 通過SmsMessageSender發送單個收件人的短信
                    SmsMessageSender sender = new SmsSingleRecipientSender(this,
                            address, msgText, threadId, status == Sms.STATUS_PENDING,
                            msgUri, subId, isExpectMore);
                    MessageUtils.markAsNotificationThreadIfNeed(this, threadId, address);

                    if(priority != -){
                        ((SmsSingleRecipientSender)sender).setPriority(priority);
                    }

                    LogTag.debugD("sendFirstQueuedMessage " + msgUri +
                            ", address: " + address +
                            ", threadId: " + threadId);

                    try {
                        // 發送
                        sender.sendMessage(SendingProgressTokenManager.NO_TOKEN);;
                        mSending.put(subscription, true);
                    } catch (MmsException e) {  //如果發送失敗,嘗試繼續發送隊列中其他的消息
                        Log.e(TAG, "sendFirstQueuedMessage: failed to send message " + msgUri
                                + ", caught ", e);
                        mSending.put(subscription, false);
                        messageFailedToSend(msgUri, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
                        success = false;
                        // Sending current message fails. Try to send more pending messages
                        // if there is any.
                        Intent intent = new Intent(SmsReceiverService.ACTION_SEND_MESSAGE, null,
                                this,
                                SmsReceiver.class);
                        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subscription);
                        sendBroadcast(intent);
                    }
                }
            } finally {
                c.close();
            }
        }
        if (success) {
            // We successfully sent all the messages in the queue. We don't need to
            // be notified of any service changes any longer.
            // In case of MSIM don't unregister service state change if there are any messages
            // pending for process on other subscriptions. There may be a chance of other
            // subscription is register and waiting for sevice state changes to process the message.
            if (!(TelephonyManager.getDefault().getPhoneCount() > ) ||
                    isUnRegisterAllowed(subscription)) {
                unRegisterForServiceStateChanges();
            }
        }
    }
           

這裡從資料庫隊列中讀取第一條,通過SmsSingleRecipientSender的sendMessage()方法發送.

  • 區分普通短信和超長短信
// packages/apps/Mms/src/com/android/mms/transaction/SmsSingleRecipientSender.java
    public boolean sendMessage(long token) throws MmsException {
        if (mMessageText == null) {
            // Don't try to send an empty message, and destination should be just
            // one.
            throw new MmsException("Null message body or have multiple destinations.");
        }
        SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(mSubId);
        ArrayList<String> messages = null;
        if ((MmsConfig.getEmailGateway() != null) &&
                (Mms.isEmailAddress(mDest) || MessageUtils.isAlias(mDest))) {
                // 彩信
            String msgText;
            msgText = mDest + " " + mMessageText;
            mDest = MmsConfig.getEmailGateway();
            messages = smsManager.divideMessage(msgText);
        } else {    // 短信
            messages = smsManager.divideMessage(mMessageText);  // 根據文本内容拆分超長短信
            // remove spaces and dashes from destination number
            // (e.g. "801 555 1212" -> "8015551212")
            // (e.g. "+8211-123-4567" -> "+82111234567")
            mDest = PhoneNumberUtils.stripSeparators(mDest);
            mDest = Conversation.verifySingleRecipient(mContext, mThreadId, mDest);
            mDest = MessageUtils.checkIdp(mContext, mDest, mSubId);
        }
        int messageCount = messages.size();

        if (messageCount == ) {
            if (!mContext.getResources().getBoolean(R.bool.enable_send_blank_message)) {
                // Don't try to send an empty message.
                throw new MmsException("SmsMessageSender.sendMessage: divideMessage returned " +
                            "empty messages. Original message is \"" + mMessageText + "\"");
            } else {
                return sendEmptyMessage();
            }
        }

        boolean moved = Sms.moveMessageToFolder(mContext, mUri, Sms.MESSAGE_TYPE_OUTBOX, );
        if (!moved) {
            throw new MmsException("SmsMessageSender.sendMessage: couldn't move message " +
                    "to outbox: " + mUri);
        }

        ArrayList<PendingIntent> deliveryIntents =  new ArrayList<PendingIntent>(messageCount);
        ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
        for (int i = ; i < messageCount; i++) {
            if (mRequestDeliveryReport && (i == (messageCount - ))) {  // 為最後一條短信添加送達報告的Intent
                // TODO: Fix: It should not be necessary to
                // specify the class in this intent.  Doing that
                // unnecessarily limits customizability.
                deliveryIntents.add(PendingIntent.getBroadcast(
                        mContext, ,
                        new Intent(
                                MessageStatusReceiver.MESSAGE_STATUS_RECEIVED_ACTION,
                                mUri,
                                mContext,
                                MessageStatusReceiver.class),
                                ));
            } else {
                deliveryIntents.add(null);
            }
            // 拆分後的短信發送完後發送該intent,進而接着發送剩餘拆分的短信
            Intent intent  = new Intent(SmsReceiverService.MESSAGE_SENT_ACTION,
                    mUri,
                    mContext,
                    SmsReceiver.class);
            intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId);
            int requestCode = ;
            if (i == messageCount -) { // 最後一條拆分的短信
                // Changing the requestCode so that a different pending intent
                // is created for the last fragment with
                // EXTRA_MESSAGE_SENT_SEND_NEXT set to true.
                requestCode = ;
                intent.putExtra(SmsReceiverService.EXTRA_MESSAGE_SENT_SEND_NEXT, true);
            }
            LogTag.debugD("sendMessage sendIntent: " + intent);
            sentIntents.add(PendingIntent.getBroadcast(mContext, requestCode, intent, ));
        }

        int validityPeriod = getValidityPeriod(mSubId);
        // Remove all attributes for CDMA international roaming.
        if (mContext.getResources().getBoolean(R.bool.config_ignore_sms_attributes) &&
                MessageUtils.isCDMAInternationalRoaming(mSubId)) {
            Log.v(TAG, "sendMessage during CDMA international roaming.");
            mPriority = -;
            deliveryIntents = null;
            validityPeriod = -;
        }
        try {
            // 通過SmsManager發送
            smsManager.sendMultipartTextMessage(mDest, mServiceCenter, messages,
                    sentIntents, deliveryIntents, mPriority, isExpectMore, validityPeriod);
        } catch (Exception ex) {
            Log.e(TAG, "SmsMessageSender.sendMessage: caught", ex);
            throw new MmsException("SmsMessageSender.sendMessage: caught " + ex +
                    " from SmsManager.sendMultipartTextMessage()");
        }
        return false;
    }
           

這裡主要是根據短信的長度調用SmsManager的方法divideMessage()來拆分長短信,添加資訊發送狀态的廣播分别由MessageStatusReceiver監聽和SmsReceiverService監聽,它們收到廣播後,從Intent中擷取發送和送達狀态,然後更新資料庫中資訊的狀态同時重新整理UI.

  • 從Application調用Framework層執行發送
// frameworks/opt/telephony/src/java/android/telephony/SmsManager.java
    public void sendMultipartTextMessage(
            String destinationAddress, String scAddress, ArrayList<String> parts,
            ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
            int priority, boolean isExpectMore, int validityPeriod) {
        if (TextUtils.isEmpty(destinationAddress)) {
            throw new IllegalArgumentException("Invalid destinationAddress");
        }
        if (parts == null || parts.size() < ) {
            throw new IllegalArgumentException("Invalid message body");
        }

        if (parts.size() > ) { //  超長短信
            try {
                 ISms iccISms = getISmsServiceOrThrow();
                if (iccISms != null) {
                    iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
                            ActivityThread.currentPackageName(), destinationAddress, scAddress,
                            parts, sentIntents, deliveryIntents, priority, isExpectMore,
                            validityPeriod);
                }
            } catch (RemoteException ex) {
                // ignore it
            }
        } else {
            //  普通短信
            PendingIntent sentIntent = null;
            PendingIntent deliveryIntent = null;
            if (sentIntents != null && sentIntents.size() > ) {
                sentIntent = sentIntents.get();
            }
            if (deliveryIntents != null && deliveryIntents.size() > ) {
                deliveryIntent = deliveryIntents.get();
            }
            sendTextMessage(destinationAddress, scAddress, parts.get(),
                    sentIntent, deliveryIntent, priority, isExpectMore, validityPeriod);
        }
    }
           
  • 發送普通短信
public void sendTextMessage(
            String destinationAddress, String scAddress, String text,
            PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
            boolean isExpectMore, int validityPeriod) {
        if (TextUtils.isEmpty(destinationAddress)) {
            throw new IllegalArgumentException("Invalid destinationAddress");
        }

        if (TextUtils.isEmpty(text)) {
            throw new IllegalArgumentException("Invalid message body");
        }

        try {
             ISms iccISms = getISmsServiceOrThrow();
            if (iccISms != null) {
                iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(),
                        ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
                        sentIntent, deliveryIntent, priority, isExpectMore, validityPeriod);
            }
        } catch (RemoteException ex) {
            // ignore it
        }
    }
           

這裡調用ISms的實作類UiccSmsController對象的sendTextForSubscriberWithOptions()方法執行發送

// frameworks/opt/telephony/src/java/com/android/internal/telephony/UiccSmsController.java
    public void sendTextForSubscriberWithOptions(int subId, String callingPackage,
            String destAddr, String scAddr, String parts, PendingIntent sentIntents,
            PendingIntent deliveryIntents, int priority, boolean isExpectMore,
            int validityPeriod) {
        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
        if (iccSmsIntMgr != null ) {
            iccSmsIntMgr.sendTextWithOptions(callingPackage, destAddr, scAddr, parts, sentIntents,
                    deliveryIntents, priority, isExpectMore, validityPeriod);
        } else {
            Rlog.e(LOG_TAG,"sendTextWithOptions iccSmsIntMgr is null for" +
                          " Subscription: " + subId);
        }
    }
           

這裡實際又通過IccSmsInterfaceManager來執行發送邏輯.

// frameworks/opt/telephony/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
    public void sendTextWithOptions(String callingPackage, String destAddr, String scAddr,
            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
            int priority, boolean isExpectMore, int validityPeriod) {
        // 判斷是否聲明發送短信權限
        mPhone.getContext().enforceCallingPermission(
                Manifest.permission.SEND_SMS,
                "Sending SMS message");
        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
            log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
                " text='"+ text + "' sentIntent=" +
                sentIntent + " deliveryIntent=" + deliveryIntent +
                "validityPeriod" + validityPeriod);
        }
        // 是否被使用者允許
        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
                callingPackage) != AppOpsManager.MODE_ALLOWED) {
            return;
        }
        // 繼續執行發送邏輯
        mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                null/*messageUri*/, callingPackage, false /*persistMessage*/, priority,
                isExpectMore, validityPeriod);
    }
           

這裡通過mDispatcher繼續執行發送邏輯.SMSDispatcher有三個實作類:CdmaSMSDispatcher、GsmSMSDispatcher、ImsSMSDispatcher.ImsSMSDispatcher相當于是對上面兩個子類的一個包裝,上層調用時主要調用ImsSMSDispatcher中的方法.ImsSMSDispatcher中再去選擇調用的是Gsm還是Cdma中的方法

@Override
    public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
            PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
            boolean persistMessage, int priority, boolean isExpectMore, int validityPeriod) {
        Rlog.d(TAG, "sendText");
        if (isCdmaMo()) {
            mCdmaDispatcher.sendText(destAddr, scAddr,
                    text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage,
                    priority, isExpectMore, validityPeriod);
        } else {
            mGsmDispatcher.sendText(destAddr, scAddr,
                    text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage,
                    priority, isExpectMore, validityPeriod);
        }
    }
           

這裡,我們分析GSM網絡的情況.

@Override
    public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
            PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
            boolean persistMessage, int priority, boolean isExpectMore, int validityPeriod) {
        // 将短信内容封裝成pdu
        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
                scAddr, destAddr, text, (deliveryIntent != null), validityPeriod);
        if (pdu != null) {
            // 将短信封裝成tracker
            HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
            SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
                    messageUri, isExpectMore, text /*fullMessageText*/,
                    true /*isText*/, validityPeriod, persistMessage, callingPkg);

            String carrierPackage = getCarrierAppPackageName();
            if (carrierPackage != null) {   // 通過運作商的app發送短信
                Rlog.d(TAG, "Found carrier package.");
                TextSmsSender smsSender = new TextSmsSender(tracker);
                smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender));
            } else {
                Rlog.v(TAG, "No carrier package.");
                sendRawPdu(tracker);    // 一般走這裡
            }
        } else {
            Rlog.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
        }
    }
           
protected void sendRawPdu(SmsTracker tracker) {
        HashMap map = tracker.getData();         // 從tracker中解析出map
        byte pdu[] = (byte[]) map.get("pdu");       // 從map中解析出pdu
        if (mSmsSendDisabled) {     // 短信發送被禁止了
            Rlog.e(TAG, "Device does not support sending sms.");
            tracker.onFailed(mContext, RESULT_ERROR_NO_SERVICE, /*errorCode*/);
            return;
        }

        if (pdu == null) {
            Rlog.e(TAG, "Empty PDU");
            tracker.onFailed(mContext, RESULT_ERROR_NULL_PDU, /*errorCode*/);
            return;
        }

        // Get calling app package name via UID from Binder call
        PackageManager pm = mContext.getPackageManager();
        String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());

        if (packageNames == null || packageNames.length == ) {
            // Refuse to send SMS if we can't get the calling package name.
            Rlog.e(TAG, "Can't get calling app package name: refusing to send SMS");
            tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, /*errorCode*/);
            return;
        }

        // Get package info via packagemanager
        PackageInfo appInfo;
        try {
            // XXX this is lossy- apps can share a UID
            appInfo = pm.getPackageInfo(packageNames[], PackageManager.GET_SIGNATURES);
        } catch (PackageManager.NameNotFoundException e) {
            Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS");
            tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, /*errorCode*/);
            return;
        }

        // checkDestination() returns true if the destination is not a premium short code or the
        // sending app is approved to send to short codes. Otherwise, a message is sent to our
        // handler with the SmsTracker to request user confirmation before sending.
        if (checkDestination(tracker)) {
            // check for excessive outgoing SMS usage by this app
            if (!mUsageMonitor.check(appInfo.packageName, SINGLE_PART_SMS)) {
                sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
                return;
            }

            if (mUsageMonitor.isSmsAuthorizationEnabled()) {
                final SmsAuthorizationCallback callback = new SmsAuthorizationCallback() {
                    @Override
                    public void onAuthorizationResult(final boolean accepted) {
                        if (accepted) {
                            sendSms(tracker);
                        } else {
                            tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE,
                                    SmsUsageMonitor.ERROR_CODE_BLOCKED);
                        }
                    }
                };
                mUsageMonitor.authorizeOutgoingSms(tracker.mAppInfo, tracker.mDestAddress,
                        tracker.mFullMessageText, callback, this);
            } else {
                sendSms(tracker);   //發送短信
            }
        }

        if (PhoneNumberUtils.isLocalEmergencyNumber(mContext, tracker.mDestAddress)) {
            new AsyncEmergencyContactNotifier(mContext).execute();
        }
    }
           

這裡對要發送的資訊及目前環境進行檢測,然後進入sendSms()流程

// GsmSMSDispatcher.java
    @Override
    protected void sendSms(SmsTracker tracker) {
        HashMap<String, Object> map = tracker.getData();

        byte pdu[] = (byte[]) map.get("pdu");

        if (tracker.mRetryCount > ) {
            Rlog.d(TAG, "sendSms: "
                    + " mRetryCount=" + tracker.mRetryCount
                    + " mMessageRef=" + tracker.mMessageRef
                    + " SS=" + mPhone.getServiceState().getState());

            // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
            //   TP-RD (bit 2) is 1 for retry
            //   and TP-MR is set to previously failed sms TP-MR
            if ((( & pdu[]) == )) {
                pdu[] |= ; // TP-RD
                pdu[] = (byte) tracker.mMessageRef; // TP-MR
            }
        }
        Rlog.d(TAG, "sendSms: "
                + " isIms()=" + isIms()
                + " mRetryCount=" + tracker.mRetryCount
                + " mImsRetry=" + tracker.mImsRetry
                + " mMessageRef=" + tracker.mMessageRef
                + " SS=" + mPhone.getServiceState().getState());
        // 發送
        sendSmsByPstn(tracker);
    }
           
@Override
    protected void sendSmsByPstn(SmsTracker tracker) {
        int ss = mPhone.getServiceState().getState();
        // if sms over IMS is not supported on data and voice is not available...
        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
            tracker.onFailed(mContext, getNotInServiceError(ss), /*errorCode*/);
            return;
        }
        HashMap<String, Object> map = tracker.getData();

        byte smsc[] = (byte[]) map.get("smsc");
        byte[] pdu = (byte[]) map.get("pdu");
        //擷取帶有EVENT_SEND_SMS_COMPLETE的Message
        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);

        // sms over gsm is used:
        //   if sms over IMS is not supported AND
        //   this is not a retry case after sms over IMS failed
        //     indicated by mImsRetry > 0
        if ( == tracker.mImsRetry && !isIms()) {
            if (tracker.mRetryCount > ) {
                // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
                //   TP-RD (bit 2) is 1 for retry
                //   and TP-MR is set to previously failed sms TP-MR
                if ((( & pdu[]) == )) {
                    pdu[] |= ; // TP-RD
                    pdu[] = (byte) tracker.mMessageRef; // TP-MR
                }
            }   //調用RILJ發送
            if (tracker.mRetryCount ==  && tracker.mExpectMore) {
                // 有多條短信在發送
                mCi.sendSMSExpectMore(IccUtils.bytesToHexString(smsc),
                        IccUtils.bytesToHexString(pdu), reply);
            } else {    // 正常發送
                mCi.sendSMS(IccUtils.bytesToHexString(smsc),
                        IccUtils.bytesToHexString(pdu), reply);
            }
        } else {    // 通過IMS發送短信
            if (!isRetryAlwaysOverIMS()) {
                mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
                        IccUtils.bytesToHexString(pdu), tracker.mImsRetry,
                        tracker.mMessageRef, reply);
            } else {
                mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
                        IccUtils.bytesToHexString(pdu), ,
                        tracker.mMessageRef, reply);
            }
            // increment it here, so in case of SMS_FAIL_RETRY over IMS
            // next retry will be sent using IMS request again.
            tracker.mImsRetry++;    //如果是重試,重試次數加1
        }
    }
           

這裡的mCi是CommandsInterface接口的實作類RIL.java,是以是調用RIL中去發送短信.這裡會攜帶reply消息,sms發送成功的消息被smsDispather接收,在handleMessage()中調用handleSendComplete()方法.

// frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java
    public void sendImsGsmSms (String smscPDU, String pdu, int retry, int messageRef,
            Message result) {
        RILRequest rr = RILRequest.obtain(RIL_REQUEST_IMS_SEND_SMS, result);

        rr.mParcel.writeInt(RILConstants.GSM_PHONE);
        rr.mParcel.writeByte((byte)retry);
        rr.mParcel.writeInt(messageRef);

        constructGsmSendSmsRilRequest(rr, smscPDU, pdu);

        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));

        mMetrics.writeRilSendSms(mInstanceId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
                SmsSession.Event.Format.SMS_FORMAT_3GPP);

        send(rr);
    }
           

sendSMSExpectMore、sendSMS、sendImsGsmSms、sendImsGsmSms這個四個方法大緻相同,都是先構造RILRequest,再調用send方法發送,差別在于傳入的請求類型不同,構造出來的pdu結構不同.最後在RILSender處理傳過來的RILRequest,通過LocalSocket(LocalSocket其通信方式與Socket差不多,隻是LocalSocket沒有跨越網絡邊界)與守護程序RILD通訊(RILD是RIL(Radio Interface Layer) Deamon的簡稱,簡單的說它下面承接GSM/GPRS Modem(電話通信子產品),上面接電話應用相關的Java庫(telephony internal)).