android 彩信接收到附件的下载原理分析

(之前有一位仁兄说博客布局在一些浏览器会出现阻挡文字,因此为了市民的方便(也就是me^0^),建议大家也用chrome来浏览,毕竟这个博客布局简明,我有点不想换)

彩信收发宏观步骤:

a、 终端A向彩信中心(MMSC)发送一条彩信,通过WAP网关POST到MMSC

b、 MMSC通过PushProxy网关,向SMSC发送PUSH消息,SMSC转发到终端B

c、 终端B通过WAP网关利用GET方法从MMSC获取一条彩信

d、 MMSC通过PushProxy网关和SNSC向终端A发送一条传送报告(delivery report)

从上面这个步骤可以看出,彩信的接收分两个步骤:

1、接收到短信。

2、分析短信然后通过http来获取彩信附件。

(续我的另一篇博客:http://www.cnblogs.com/not-code/archive/2011/11/27/2265287.html

因此彩信第一步跟短信的接收流程一样,在RILReceiver 接收到短信转到processUnsolicited进行处理。

GSM方式(最近才知道短信的收发有两种,一种就是通过GSM,另一种是通过CDMA):GSM其事件类型为RIL_UNSOL_RESPONSE_NEW_SMS。先调用responseString从Parcel中获取数据,再使用 newFromCMT方法获取SmsMessage对象,最后调用mSMSRegistrant的notifyRegistrant方法设置消息类型 (what属性为EVENT_NEW_SMS)并转到SMSDispatcher进行处理。这个时候就会调用子类(GsmSMSDispatcher)的dispatchMessage方法处理。

 

/** {@inheritDoc} */
    protected int dispatchMessage(SmsMessageBase smsb) {
        // If sms is null, means there was a parsing error.
        if (smsb == null) {
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }
        SmsMessage sms = (SmsMessage) smsb;
        boolean handled = false;

        if (sms.isTypeZero()) {
            // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
            // Displayed/Stored/Notified. They should only be acknowledged.
            Log.d(TAG, "Received short message type 0, Don't display or store it. Send Ack");
            return Intents.RESULT_SMS_HANDLED;
        }

        // Special case the message waiting indicator messages
        if (sms.isMWISetMessage()) {
            mGsmPhone.updateMessageWaitingIndicator(true);
            handled = sms.isMwiDontStore();
            if (Config.LOGD) {
                Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
            }
        } else if (sms.isMWIClearMessage()) {
            mGsmPhone.updateMessageWaitingIndicator(false);
            handled = sms.isMwiDontStore();
            if (Config.LOGD) {
                Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
            }
        }

        if (handled) {
            return Intents.RESULT_SMS_HANDLED;
        }

        if (!mStorageAvailable && (sms.getMessageClass() != MessageClass.CLASS_0)) {
            // It's a storable message and there's no storage available.  Bail.
            // (See TS 23.038 for a description of class 0 messages.)
            return Intents.RESULT_SMS_OUT_OF_MEMORY;
        }

        SmsHeader smsHeader = sms.getUserDataHeader();
         // See if message is partial or port addressed.
        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
            // Message is not partial (not part of concatenated sequence).
            byte[][] pdus = new byte[1][];
            pdus[0] = sms.getPdu();

            if (smsHeader != null && smsHeader.portAddrs != null) {
                if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
                    return mWapPush.dispatchWapPdu(sms.getUserData());
                } else {
                    // The message was sent to a port, so concoct a URI for it.
                    dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
                }
            } else {
                // Normal short and non-port-addressed message, dispatch it.
                dispatchPdus(pdus);
            }
            return Activity.RESULT_OK;
        } else {
            // Process the message part.
            return processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
        }
    }

 

在这个方法里,将会判断接收到的短信是否长短信、是否彩信、是否普通短信。首先获取SmsHeader,
如果SmsHeader或SmsHeader.concatRef均不为空,说明是长短信,则调用processMessagePart将短信分段存入raw表,待所有分段都收到后,将其组装。然后根据端口的不同,按照彩信通知(WapPushOverSms的dispatchWapPdu方法)、指定端口的彩信(dispatchPortAddressedPdus)、长短信(dispatchPdus)进行分发处理。

 

(从网上窃的一副图~~)(左边是GSM的处理,右边是CDMA的处理,两个处理类都是继承了SMSDispatcher)

image

继续分析彩信,如果是彩信就调用WapPushOverSmsdispatchWapPdu的方法。(该类的位置)

image

 


    public int dispatchWapPdu(byte[] pdu) {

        if (Config.DEBUG) Log.d(LOG_TAG, "Rx: " + IccUtils.bytesToHexString(pdu));

        int index = 0;
        int transactionId = pdu[index++] & 0xFF;
        int pduType = pdu[index++] & 0xFF;
        int headerLength = 0;

        if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) &&
                (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
            if (Config.DEBUG) Log.w(LOG_TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
            return Intents.RESULT_SMS_HANDLED;
        }

        pduDecoder = new WspTypeDecoder(pdu);

        /**
         * Parse HeaderLen(unsigned integer).
         * From wap-230-wsp-20010705-a section 8.1.2
         * The maximum size of a uintvar is 32 bits.
         * So it will be encoded in no more than 5 octets.
         */
        if (pduDecoder.decodeUintvarInteger(index) == false) {
            if (Config.DEBUG) Log.w(LOG_TAG, "Received PDU. Header Length error.");
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }
        headerLength = (int)pduDecoder.getValue32();
        index += pduDecoder.getDecodedDataLength();

        int headerStartIndex = index;

        /**
         * Parse Content-Type.
         * From wap-230-wsp-20010705-a section 8.4.2.24
         *
         * Content-type-value = Constrained-media | Content-general-form
         * Content-general-form = Value-length Media-type
         * Media-type = (Well-known-media | Extension-Media) *(Parameter)
         * Value-length = Short-length | (Length-quote Length)
         * Short-length = <Any octet 0-30>   (octet <= WAP_PDU_SHORT_LENGTH_MAX)
         * Length-quote = <Octet 31>         (WAP_PDU_LENGTH_QUOTE)
         * Length = Uintvar-integer
         */
        if (pduDecoder.decodeContentType(index) == false) {
            if (Config.DEBUG) Log.w(LOG_TAG, "Received PDU. Header Content-Type error.");
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }

        String mimeType = pduDecoder.getValueString();
        long binaryContentType = pduDecoder.getValue32();
        index += pduDecoder.getDecodedDataLength();

        byte[] header = new byte[headerLength];
        System.arraycopy(pdu, headerStartIndex, header, 0, header.length);

        byte[] intentData;

        if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO)) {
            intentData = pdu;
        } else {
            int dataIndex = headerStartIndex + headerLength;
            intentData = new byte[pdu.length - dataIndex];
            System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length);
        }

        /**
         * Seek for application ID field in WSP header.
         * If application ID is found, WapPushManager substitute the message
         * processing. Since WapPushManager is optional module, if WapPushManager
         * is not found, legacy message processing will be continued.
         */
        if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) {
            index = (int) pduDecoder.getValue32();
            pduDecoder.decodeXWapApplicationId(index);
            String wapAppId = pduDecoder.getValueString();
            if (wapAppId == null) {
                wapAppId = Integer.toString((int) pduDecoder.getValue32());
            }
            String contentType = ((mimeType == null) ?
                                  Long.toString(binaryContentType) : mimeType);
            if (Config.DEBUG) Log.v(LOG_TAG, "appid found: " + wapAppId + ":" + contentType);
            try {
                boolean processFurther = true;
                IWapPushManager wapPushMan = mWapConn.getWapPushManager();

                if (wapPushMan == null) {
                    if (Config.DEBUG) Log.w(LOG_TAG, "wap push manager not found!");
                } else {
                    Intent intent = new Intent();
                    intent.putExtra("transactionId", transactionId);
                    intent.putExtra("pduType", pduType);
                    intent.putExtra("header", header);
                    intent.putExtra("data", intentData);
                    intent.putExtra("contentTypeParameters",
                            pduDecoder.getContentParameters());
                    int procRet = wapPushMan.processMessage(wapAppId, contentType, intent);
                    if (Config.DEBUG) Log.v(LOG_TAG, "procRet:" + procRet);
                    if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0
                        && (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) {
                        processFurther = false;
                    }
                }
                if (!processFurther) {
                    return Intents.RESULT_SMS_HANDLED;
                }
            } catch (RemoteException e) {
                if (Config.DEBUG) Log.w(LOG_TAG, "remote func failed...");
            }
        }
        if (Config.DEBUG) Log.v(LOG_TAG, "fall back to existing handler");

        if (mimeType == null) {
            if (Config.DEBUG) Log.w(LOG_TAG, "Header Content-Type error.");
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }
        String permission;
        if (mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_MMS)) {
            permission = "android.permission.RECEIVE_MMS";
        } else {
            permission = "android.permission.RECEIVE_WAP_PUSH";
        }
        Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION);
        intent.setType(mimeType);
        intent.putExtra("transactionId", transactionId);
        intent.putExtra("pduType", pduType);
        intent.putExtra("header", header);
        intent.putExtra("data", intentData);
        intent.putExtra("contentTypeParameters", pduDecoder.getContentParameters());
        mSmsDispatcher.dispatch(intent, permission);
        return Activity.RESULT_OK;
    }

该方法比较长,我们直接看最后的一步:调用父类的dispatch将信息广播出去。

Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION);

intent.setType(mimeType);

intent.putExtra("transactionId", transactionId);

intent.putExtra("pduType", pduType);

intent.putExtra("header", header);

intent.putExtra("data", intentData);

intent.putExtra("contentTypeParameters", pduDecoder.getContentParameters());

mSmsDispatcher.dispatch(intent, permission);

应用层PushReceiver类的onReceive将被调用,让屏幕亮5秒,然后创建一个ReceivePushTask并使用它的execute方法。ReceivePushTask是一个AsyncTask,实现了doInBackground方法。当传入intent后,会在doInBackground中将其中的数据转成GenericPdu,并根据其消息类型做出不同的操作。
如果是发送报告或已读报告,将其存入数据库。
如果是彩信通知,若已存在,则不处理。否则将其存入数据库。启动TransactionService进行处理。

 

case MESSAGE_TYPE_NOTIFICATION_IND: {
    NotificationInd nInd = (NotificationInd) pdu;

    if (MmsConfig.getTransIdEnabled()) {
        byte [] contentLocation = nInd.getContentLocation();
        if ('=' == contentLocation[contentLocation.length - 1]) {
            byte [] transactionId = nInd.getTransactionId();
            byte [] contentLocationWithId = new byte [contentLocation.length
                                                      + transactionId.length];
            System.arraycopy(contentLocation, 0, contentLocationWithId,
                    0, contentLocation.length);
            System.arraycopy(transactionId, 0, contentLocationWithId,
                    contentLocation.length, transactionId.length);
            nInd.setContentLocation(contentLocationWithId);
        }
    }

    if (!isDuplicateNotification(mContext, nInd)) {
        Uri uri = p.persist(pdu, Inbox.CONTENT_URI);
        // Start service to finish the notification transaction.
        Intent svc = new Intent(mContext, TransactionService.class);
        svc.putExtra(TransactionBundle.URI, uri.toString());
        svc.putExtra(TransactionBundle.TRANSACTION_TYPE,
                Transaction.NOTIFICATION_TRANSACTION);
        mContext.startService(svc);
    } else if (LOCAL_LOGV) {
        Log.v(TAG, "Skip downloading duplicate message: "
                + new String(nInd.getContentLocation()));
    }
    break;
}

启动TransactionService服务,在onStartCommand中调用launchTransaction方法。

private void launchTransaction(int serviceId, TransactionBundle txnBundle,
            boolean noNetwork) {
        if (noNetwork) {
            Log.w(TAG, "launchTransaction: no network error!");
            onNetworkUnavailable(serviceId, txnBundle.getTransactionType());
            return;
        }
        Message msg = mServiceHandler.obtainMessage(EVENT_TRANSACTION_REQUEST);
        msg.arg1 = serviceId;
        msg.obj = txnBundle;

        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
            Log.v(TAG, "launchTransaction: sending message " + msg);
        }
        mServiceHandler.sendMessage(msg);
    }

接着以what= EVENT_TRANSACTION_REQUEST的参数运行mServiceHandler,在mServiceHandler的处理中将创建NotificationTransaction类的对象,经一系列的判断最后将调用processTransaction方法处理NotificationTransaction对象。

private boolean processTransaction(Transaction transaction)
            throws IOException {
        // Check if transaction already processing
        synchronized (mProcessing) {
            for (Transaction t : mPending) {
                if (t.isEquivalent(transaction)) {
                    if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                        Log.v(TAG, "Transaction already pending: "
                                + transaction.getServiceId());
                    }
                    return true;
                }
            }
            for (Transaction t : mProcessing) {
                if (t.isEquivalent(transaction)) {
                    if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                        Log.v(TAG,
                                "Duplicated transaction: "
                                        + transaction.getServiceId());
                    }
                    return true;
                }
            }

            /*
             * Make sure that the network connectivity necessary for MMS traffic
             * is enabled. If it is not, we need to defer processing the
             * transaction until connectivity is established.
             */
            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                Log.v(TAG, "processTransaction: call beginMmsConnectivity...");
            }
            int connectivityResult = beginMmsConnectivity();
            if (connectivityResult == Phone.APN_REQUEST_STARTED) {
                mPending.add(transaction);
                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                    Log.v(TAG,
                            "processTransaction: connResult=APN_REQUEST_STARTED, "
                                    + "defer transaction pending MMS connectivity");
                }
                return true;
            }

            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                Log.v(TAG, "Adding transaction to 'mProcessing' list: "
                        + transaction);
            }
            mProcessing.add(transaction);
        }

        // Set a timer to keep renewing our "lease" on the MMS connection
        sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
                APN_EXTENSION_WAIT);

        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
            Log.v(TAG, "processTransaction: starting transaction "
                    + transaction);
        }

        // Attach to transaction and process it
        transaction.attach(TransactionService.this);
        transaction.process();
        return true;
    }

该方法首先会先判断该Transaction对象是否存在mPending或mProcessing队列中,如果没有则将对象加入到mProcessing中,并将TransactionService本身加入到NotificationTransaction对象的观察者列表(这样做的目的是为了后面下载完成后通知该服务TransactionService的mProcessing移除掉NotificationTransaction对象并发送完成下载的广播)。最后将调用NotificationTransaction的process方法。

public void process() {
        new Thread(this).start();
    }

    public void run() {
        DownloadManager downloadManager = DownloadManager.getInstance();
        boolean autoDownload = downloadManager.isAuto();
        boolean dataSuspended = (TelephonyManager.getDefault().getDataState() == TelephonyManager.DATA_SUSPENDED);
        try {
            if (LOCAL_LOGV) {
                Log.v(TAG, "Notification transaction launched: " + this);
            }

            // By default, we set status to STATUS_DEFERRED because we
            // should response MMSC with STATUS_DEFERRED when we cannot
            // download a MM immediately.
            int status = STATUS_DEFERRED;
            // Don't try to download when data is suspended, as it will fail, so
            // defer download
            if (!autoDownload || dataSuspended) {
                downloadManager
                        .markState(mUri, DownloadManager.STATE_UNSTARTED);
                sendNotifyRespInd(status);
                return;
            }

            downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING);

            if (LOCAL_LOGV) {
                Log.v(TAG, "Content-Location: " + mContentLocation);
            }

            byte[] retrieveConfData = null;
            // We should catch exceptions here to response MMSC
            // with STATUS_DEFERRED.
            try {
                retrieveConfData = getPdu(mContentLocation);
            } catch (IOException e) {
                mTransactionState.setState(FAILED);
            }

            if (retrieveConfData != null) {
                GenericPdu pdu = new PduParser(retrieveConfData).parse();
                if ((pdu == null)
                        || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) {
                    Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU.");
                    mTransactionState.setState(FAILED);
                    status = STATUS_UNRECOGNIZED;
                } else {
                    // Save the received PDU (must be a M-RETRIEVE.CONF).
                    PduPersister p = PduPersister.getPduPersister(mContext);
                    Uri uri = p.persist(pdu, Inbox.CONTENT_URI);
                    // We have successfully downloaded the new MM. Delete the
                    // M-NotifyResp.ind from Inbox.
                    SqliteWrapper.delete(mContext,
                            mContext.getContentResolver(), mUri, null, null);
                    // Notify observers with newly received MM.
                    mUri = uri;
                    status = STATUS_RETRIEVED;
                }
            }

            if (LOCAL_LOGV) {
                Log.v(TAG, "status=0x" + Integer.toHexString(status));
            }

            // Check the status and update the result state of this Transaction.
            switch (status) {
            case STATUS_RETRIEVED:
                mTransactionState.setState(SUCCESS);
                break;
            case STATUS_DEFERRED:
                // STATUS_DEFERRED, may be a failed immediate retrieval.
                if (mTransactionState.getState() == INITIALIZED) {
                    mTransactionState.setState(SUCCESS);
                }
                break;
            }

            sendNotifyRespInd(status);

            // Make sure this thread isn't over the limits in message count.
            Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(
                    mContext, mUri);
        } catch (Throwable t) {
            Log.e(TAG, Log.getStackTraceString(t));
        } finally {
            mTransactionState.setContentUri(mUri);
            if (!autoDownload || dataSuspended) {
                // Always mark the transaction successful for deferred
                // download since any error here doesn't make sense.
                mTransactionState.setState(SUCCESS);
            }
            if (mTransactionState.getState() != SUCCESS) {
                mTransactionState.setState(FAILED);
                Log.e(TAG, "NotificationTransaction failed.");
            }
            notifyObservers();
        }
    }

NotificationTransaction的process方法将下载相应彩信,首先删除彩信通知,通知mmsc,删除超过容量限制的彩信,彩信附件的获取最终是通过getPdu(mContentLocation)来请求附件的流,返回byte[]类型。最后将notifyObservers()通知TransactionService处理其余待发送的彩信和发送下载完成的广播。

看图比较容易理解:(网上窃的一副妙图~~,不过里面最后那里好像有一个小错误,不过没关系~~)

image

(彩信接收主要大概涉及到的类的类图,这是原创滴,原创就是简陋)

image

RIL到SMSDispatch中间其实涉及很多步骤,这里就简明一点,抽丝剥茧,让大家对彩信接收涉及到的类的分布和以及它们的作用有个大概的蓝图。短信来后发现自己属于GsmSMSDispatch类,WapPushOverSms实例调用dispatchWapPdu发送广播,PushReceive接收到广播后启动TransactionService服务,TransactionService将自己attach到mObservers的观察列表中,然后调用NotificationTransaction对象的process方法,该方法将通过getPdu来获取附件内容,最后将调用notifyObservers通知所有添加到观察列表的对象调用update方法实现更新。 (这个就是传说中的观察者模式,改网上的一个例子:邮局现在有少男少女和花花公子的杂志(杂志是被观察者,里面维护一条订阅者的队列),现在有一个女孩想订阅少男少女(女孩是观察者,订阅就是将自己add注册到被观察者的队列),一个男孩订阅少男少女和花花公子,当邮局某本杂志到来就会通知到订阅者(通知就是循环队列通知每个订阅该杂志的人,然后将这本杂志作为变量传过去,这样订阅者就知道是哪本杂志到来)。 同理:TransactionService是观察者(男孩或者女孩),NotificationTransaction是被观察者(杂志))

 

参考和引用部分文字和图片的文章:

http://www.diybl.com/course/3_program/java/android/20111124/562797.html

http://www.2cto.com/kf/201111/109802.html

posted @ 2011-12-01 17:02  没有代码  阅读(7759)  评论(1编辑  收藏  举报