1. 程式人生 > >Android簡訊傳送流程之長簡訊傳送(原)

Android簡訊傳送流程之長簡訊傳送(原)

        從前面《Android簡訊傳送流程之普通簡訊傳送》流程看到,長簡訊與普通簡訊的流程從SmsManager的sendMultipartTextMessage()方法開始區分,現在我們來看長簡訊的流程:
        @SmsManager.java
        public void sendMultipartTextMessage( String destinationAddress, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
            if (TextUtils.isEmpty(destinationAddress)) {
                throw new IllegalArgumentException("Invalid destinationAddress");
            }
            if (parts == null || parts.size() < 1) {
                throw new IllegalArgumentException("Invalid message body");
            }


            if (parts.size() > 1) {
                //長簡訊傳送
                try {
                    ISms iccISms = getISmsServiceOrThrow();
                    iccISms.sendMultipartText(ActivityThread.currentPackageName(),
                            destinationAddress, scAddress, parts,
                            sentIntents, deliveryIntents);
                } catch (RemoteException ex) {
                }
            } else {
                //普通簡訊傳送
                PendingIntent sentIntent = null;
                PendingIntent deliveryIntent = null;
                if (sentIntents != null && sentIntents.size() > 0) {
                    sentIntent = sentIntents.get(0);
                }
                if (deliveryIntents != null && deliveryIntents.size() > 0) {
                    deliveryIntent = deliveryIntents.get(0);
                }
                sendTextMessage(destinationAddress, scAddress, parts.get(0), sentIntent, deliveryIntent);
            }
        }
        在上面的方法中,對於長簡訊將會通過iccISms物件也就是UiccSmsController的sendMultipartText()方法傳送出去:
        @UiccSmsController.java
        public void sendMultipartText(String callingPackage, String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) throws android.os.RemoteException {
            sendMultipartTextForSubscriber(getPreferredSmsSubscription(), callingPackage, destAddr, scAddr, parts, sentIntents, deliveryIntents);
        }
        public void sendMultipartTextForSubscriber(long subId, String callingPackage, String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) throws android.os.RemoteException {
            IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
            if (iccSmsIntMgr != null ) {
                iccSmsIntMgr.sendMultipartText(callingPackage, destAddr, scAddr, parts, sentIntents,
                        deliveryIntents);
            } else {
            }
        }
        接下來UiccSmsController又把流程交給IccSmsInterfaceManager的sendMultipartText()來處理:
        @IccSmsInterfaceManager.java
        public void sendMultipartText(String callingPackage, String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
            //許可權檢查
            mPhone.getContext().enforceCallingPermission( Manifest.permission.SEND_SMS, "Sending SMS message");
            if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) {
                return;
            }


            if (parts.size() > 1 && parts.size() < 10 && !SmsMessage.hasEmsSupport()) {
                //當前運營商不支援長短新,需要自行將簡訊分割後分別傳送
                for (int i = 0; i < parts.size(); i++) {
                    // If EMS is not supported, we have to break down EMS into single segment SMS
                    // and add page info " x/y".
                    String singlePart = parts.get(i);
                    if (SmsMessage.shouldAppendPageNumberAsPrefix()) {
                        singlePart = String.valueOf(i + 1) + '/' + parts.size() + ' ' + singlePart;
                    } else {
                        singlePart = singlePart.concat(' ' + String.valueOf(i + 1) + '/' + parts.size());
                    }


                    PendingIntent singleSentIntent = null;
                    if (sentIntents != null && sentIntents.size() > i) {
                        singleSentIntent = sentIntents.get(i);
                    }


                    PendingIntent singleDeliveryIntent = null;
                    if (deliveryIntents != null && deliveryIntents.size() > i) {
                        singleDeliveryIntent = deliveryIntents.get(i);
                    }


                    //將長簡訊分割,挨個傳送
                    mDispatcher.sendText(destAddr, scAddr, singlePart,
                            singleSentIntent, singleDeliveryIntent,
                            null/*messageUri*/, callingPackage);
                }
                return;
            }
            //運營商支援長簡訊,直接傳送即可
            mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
                    (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents,
                    null/*messageUri*/, callingPackage);
        }
        從上面來看,對於長簡訊,需要區分運營商是否支援的情況,如果不支援,需要我們將簡訊分割後逐條傳送,如果支援,需要走不同流程,由於逐條傳送時的流程與普通簡訊傳送流程相同,因此這裡主要分析以下運營商支援長簡訊的情況,也就時sendMultipartText()的流程:
        @ImsSMSDispatcher.java
        protected void sendMultipartText(String destAddr, String scAddr,
                ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
                ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg) {
            if (isCdmaMo()) {
                //CDMA
                mCdmaDispatcher.sendMultipartText(destAddr, scAddr,
                        parts, sentIntents, deliveryIntents, messageUri, callingPkg);
            } else {
                //GSM
                mGsmDispatcher.sendMultipartText(destAddr, scAddr,
                        parts, sentIntents, deliveryIntents, messageUri, callingPkg);
            }
        }
        和普通簡訊類似,也許要區分當前的網路環境,對於GSM來說,就是使用GsmSMSDispatcher來繼續處理,這個處理是在GsmSMSDispatcher父類SMSDispatcher中完成的:
        @SMSDispatcher.java
        protected void sendMultipartText(String destAddr, String scAddr, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg) {
            //將簡訊移入或寫入發件箱
            if (messageUri == null) {
                if (SmsApplication.shouldWriteMessageForPackage(callingPkg, mContext)) {
                    messageUri = writeOutboxMessage(
                            getSubId(),
                            destAddr,
                            getMultipartMessageText(parts),
                            deliveryIntents != null && deliveryIntents.size() > 0,
                            callingPkg);
                }
            } else {
                moveToOutbox(getSubId(), messageUri, callingPkg);
            }
            int refNumber = getNextConcatenatedRef() & 0x00FF;
            int msgCount = parts.size();
            int encoding = SmsConstants.ENCODING_UNKNOWN;


            TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
            for (int i = 0; i < msgCount; i++) {
                TextEncodingDetails details = calculateLength(parts.get(i), false);
                if (encoding != details.codeUnitSize
                        && (encoding == SmsConstants.ENCODING_UNKNOWN
                            || encoding == SmsConstants.ENCODING_7BIT)) {
                    encoding = details.codeUnitSize;
                            }
                encodingForParts[i] = details;
            }


            // States to track at the message level (for all parts)
            final AtomicInteger unsentPartCount = new AtomicInteger(msgCount);
            final AtomicBoolean anyPartFailed = new AtomicBoolean(false);


            for (int i = 0; i < msgCount; i++) {
                SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
                concatRef.refNumber = refNumber;
                concatRef.seqNumber = i + 1;  // 1-based sequence
                concatRef.msgCount = msgCount;
                // TODO: We currently set this to true since our messaging app will never
                // send more than 255 parts (it converts the message to MMS well before that).
                // However, we should support 3rd party messaging apps that might need 16-bit
                // references
                // Note:  It's not sufficient to just flip this bit to true; it will have
                // ripple effects (several calculations assume 8-bit ref).
                concatRef.isEightBits = true;
                SmsHeader smsHeader = new SmsHeader();
                smsHeader.concatRef = concatRef;


                // Set the national language tables for 3GPP 7-bit encoding, if enabled.
                if (encoding == SmsConstants.ENCODING_7BIT) {
                    smsHeader.languageTable = encodingForParts[i].languageTable;
                    smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
                }


                PendingIntent sentIntent = null;
                if (sentIntents != null && sentIntents.size() > i) {
                    sentIntent = sentIntents.get(i);
                }


                PendingIntent deliveryIntent = null;
                if (deliveryIntents != null && deliveryIntents.size() > i) {
                    deliveryIntent = deliveryIntents.get(i);
                }


                //逐條傳送
                sendNewSubmitPdu(destAddr, scAddr, parts.get(i), smsHeader, encoding,
                        sentIntent, deliveryIntent, (i == (msgCount - 1)),
                        unsentPartCount, anyPartFailed, messageUri);
            }
        }
        在上面的過程中我們看到,對於運營商支援的長簡訊情況,需要把拆分出來的簡訊分別加上簡訊頭編碼,也就是SmsHeader,然後分別呼叫sendNewSubmitPdu()方法進行傳送。
        這裡需要簡單介紹以下SmsHeader作用,普通的簡訊中SmsHeader為空,所以只有長簡訊才會有該資料。他內部確定了該長簡訊分組的大小、每個分組的索引、編碼格式等資訊。
        接下來看sendNewSubmitPdu()的過程,這個方法是在GsmSMSDispatcher中實現的:
        protected void sendNewSubmitPdu(String destinationAddress, String scAddress, String message, SmsHeader smsHeader, int encoding, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart, AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri) {
            //對簡訊內容進行編碼
            SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
                    message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
                    encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
            if (pdu != null) {
                HashMap map =  getSmsTrackerMap(destinationAddress, scAddress, message, pdu);
                //傳送
                SmsTracker tracker = getSmsTracker(map, sentIntent,
                        deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
                        smsHeader, !lastPart);
                sendRawPdu(tracker);
            } else {
                Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
            }
        }
        接下來的流程和普通簡訊一樣,最終通過RILJ將簡訊傳送出去,並且註冊回撥訊息為EVENT_SEND_SMS_COMPLETE。
        也就是說,對於長簡訊而言,如果運營商不支援,那麼就拆分為一個個普通簡訊然後逐條傳送,如果運營商支援長簡訊,則會對每個分組簡訊新增SmsHeader的資訊頭,然後逐條傳送。

        所以當SMSDispatcher接收到EVENT_SEND_SMS_COMPLETE訊息時,就說明,無論是普通簡訊或者長簡訊,都已經發送完畢。

        以上就是長簡訊的傳送流程。