彩信的接收简介

主要是由应用程序负责从彩信服务中心(MMSC Multimedia Messaging Service Center)下载彩信信息。大致的流程是Frameworks会先发出一条短信,告知应用程序有一个彩信,短信中含有一些信息比如过期日期,发送者手机号码,彩信的URL等,然后应用程序自行通过HTTP取回URL所指的彩信内容。具体的流程为:

Telephony Frameworks会先发出一个Intent:android.provider.Telephony.Intents.WAP_PUSH_RECEIVED_ACTION=”android.provider.Telephony.WAP_PUSH_RECEIVED”告知上层应用有一个彩信来了。这个Intent中会含有一个”data”byte数组(通过byte[] data = intent.getByteArrayExtra(“data”)来获取),这个Byte数组是关于这个彩信的一些信息的描述,它是一个NotificationInd,里面含有彩信的一些信息,比如发送者手机号码,彩信的ContentLocation(URL)。之后是由应用程序来决定如何做下一步的处理。

在Mms中是由transaction.PushReceiver.java来接收WAP_PUSH_RECEIVED_ACTION,接收到彩信通知Intent后,它会做一些预处理,把data字段取出来,用Pdu的工具解析成为GenericPdu,然后转化为NotificationInd,并把它写入数据库,然后会启动TransactionService来做进一步的NOTIFICATION_TRANSACTION处理,同时把这个NotificationInd的Uri也传过去。

TransactionService被唤起,在其onStartCommand中会处理一下把PushReceiver所传来的Intent放入自己的MessageQueue中,然后在Handler.handleMessage()中处理TRANSACTION_REQUEST时处理NOTIFICATION_TRANSACTION。先是加载默认的一些彩信相关的配置信息,主要是MMSC,Proxy和Port,这些都是与运营商相关的信息,可以通过APN的设置来更改。TransactionService用PushReciver传来的NotificationInd的Uri和加载的配置信息TransactionSettings构建一个NotificationTransaction对象。之后,TransactionService检查其内的二个队列,或是加入Pending队列,或是直接处理(加入到正在处理队列),处理也是直接调用NotificationTransaction.process()。

NotificationTransaction的process()方法是继承自父类Transaction的方法,它只是简单的开启一个新的线程,然后返回,这样就可以让Service去处理其他的Transaction Request了。

在线程中,首先从DownloadManager和TelephonyManager中加载一些配置信息,是否彩信设置为自动获取(auto retrieve),以及Telephony是否设置为数据延迟(DATA_SUSPEND),然后会采取不同的措施,再从NotificationInd中取出彩信的过期日期。如果配置为不取数据(更确切的说,是不现在取数据),那么就先给DownloadManager的状态标记为STATE_UNSTARTED,再给MMSC发送一个Notify
Response Indication,之后结束处理,函数返回,彩信的通知处理流程到此为止。用户可以通过操作UI,用其他方法手动下载彩信,这个会在后面详细讨论。

如果设置为自动获取或者数据传输是畅通的,那么就把DownloadManager状态标记为START_DOWNLOADING并开始下载彩信数据。彩信的获取是通过HTTP到彩信的ContentLocation(URL)取得数据。先是调用父类方法getPdu(),传入彩信的URL,最终调用HttpUtils的httpConnection方法发送HTTP GET请求,MMSC会把彩信数据返回,作为getPdu()的返回值返回。拿到的是一个byte数组,需要用Pdu的工具解析成为GenericPdu,然后用PduPersister把其写入数据库,再把彩信的大小更新到数据库,到这里一个彩信的接收就算完成了。剩下的就是,因为已经获得了彩信的数据,所以要把先前的通知信息(NotificationInd)删除掉,然后更新一下相关的状态,给MMSC返回Notify
Response Indication,结束处理。

如前所述,如果彩信配置设置为不自动获取,那么UI刷新了后就会显示彩信通知:到期日期,彩信大小等,并提供一个”Download”按扭。用户可以点击按扭来下载彩信内容,点击按扭后,会启动TransactionService,把彩信通知的Uri,和RETRIEVE_TRANSACTION request打包进一个Intent传给TransactionService。TransactionService,像处理其他的Transaction一样,都是放进自己的MessageQueue,然后加载默认的TransactionSettings,构建RetrieveTransaction对象,然后处理调用RetrieveTransaction.process()。

RetrieveTransaction也是继承自Transaction,其process()也是创建一个线程,然后返回。在线程中,首先它用Pdu工具根据Uri从数据库中加载出彩信通知(NotificationInd),从NotificationInd中取得彩信的过期日期,检查过期日期,如果彩信已经过期,那么给MMSC发送Notify Response Indication。把DownloadManager状态标记为开始下载,然后如果彩信已过期,标记Transaction状态为Failed,然后返回,结束处理流程。如果一切正常,会用getPdu()从彩信的ContentLocation(URL)上面获取彩信内容,它会用HttpUtils.httpConnection()通过HTTP来获取,返回一个byte数组。用Pdu工具解析byte数组,得到GenericPdu,检查一下是否是新信息,是否是重复的信息,如果重复,标记状为失败,然后返回,结束处理。如果是新信息,先把GenericPdu用PduPersister写入数据库中,更新信息大小和ContentLocation(URL)到数据库中,到这里一个彩信其实已经全部获取完了。接下来就是发送收到确认信息给MMSC,标记处理状态为成功,结束处理。这时UI应该监听到数据库变化,并刷新,新信息应该会显示给用户。

在分析代码之前,也是首先与大家分享一下在网络上很流行的两张顺序图,本人也受到了很大的启发。

深度分析:Android4.3下MMS发送到附件为音频文件(音频为系统内置音频)的彩信给自己,添加音频-发送彩信-接收彩信-下载音频附件-预览-播放(三,接收彩信<2,下载彩信>)-LMLPHP

深度分析:Android4.3下MMS发送到附件为音频文件(音频为系统内置音频)的彩信给自己,添加音频-发送彩信-接收彩信-下载音频附件-预览-播放(三,接收彩信<2,下载彩信>)-LMLPHP

android的彩信接收应用层部分从PushReceiver类开始。当onReceive被调用后,让屏幕亮5秒( wl.acquire(5000);),然后创建一个ReceivePushTask并使用它的execute方法。ReceivePushTask(内部类)是一个AsyncTask,实现了doInBackground()方法。根据消息类型做出相应的处理。

调用PushReceiver.java类中的doInBackground()方法,部分代码如下:《TAG 2-1》

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)) {

                            int subId = intent.getIntExtra(MSimConstants.SUBSCRIPTION_KEY, 0);

                            ContentValues values = new ContentValues(1);

                            values.put(Mms.SUB_ID, subId);



                            Uri uri = p.persist(pdu, Inbox.CONTENT_URI,

                                    true,

                                    MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext),

                                    null);



                            SqliteWrapper.update(mContext, cr, uri, values, null, null);

                            if (MessageUtils.isMobileDataDisabled(mContext) &&

                                    !MessageUtils.CAN_SETUP_MMS_DATA) {

                                MessagingNotification.nonBlockingUpdateNewMessageIndicator(mContext,

                                        MessagingNotification.THREAD_ALL, false);

                            }

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

                            svc.putExtra(Mms.SUB_ID, subId); //destination sub id

                            svc.putExtra(MultiSimUtility.ORIGIN_SUB_ID,

                                    MultiSimUtility.getCurrentDataSubscription(mContext));



                            if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) {

                                boolean isSilent = true; //default, silent enabled.

                                if ("prompt".equals(

                                    SystemProperties.get(

                                        TelephonyProperties.PROPERTY_MMS_TRANSACTION))) {

                                    isSilent = false;

                                }



                                if (isSilent) {

                                    Log.d(TAG, "MMS silent transaction");

                                    Intent silentIntent = new Intent(mContext,

                                            com.android.mms.ui.SelectMmsSubscription.class);

                                    silentIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                                    silentIntent.putExtras(svc); //copy all extras

                                    mContext.startService(silentIntent);



                                } else {

                                    Log.d(TAG, "MMS prompt transaction");

                                    triggerPendingOperation(svc, subId);

                                }

                            } else {

                                mContext.startService(svc);

                            }





                        } else if (LOCAL_LOGV) {

                            Log.v(TAG, "Skip downloading duplicate message: "

                                    + new String(nInd.getContentLocation()));

                        }

                        break;

                    }

doInBackground中将其中的数据转成GenericPdu,并根据其消息类型做出不同的操作。如果是发送报告或已读报告,将其存入数据库。如果是彩信通知,若已存在,则不处理。否则将其存入数据库。启动TransactionService进行处理。TransactionService中的处理主要是调用mServiceHandler,大体过程与发送彩信时相同,只是此处创建的是NotificationTransaction。如果不支持自动下载或数据传输没打开,仅通知mmsc。否则,下载相应彩信,删除彩信通知,通知mmsc,删除超过容量限制的彩信,通知TransactionService处理其余待发送的彩信。

我们接着进入TransactionService.java类中进行分析,启动服务后,在onStartCommand()方法中接收Intent并进行封装并放入自己的MessageQueue中,在Handler的handleMessgae中进行处理:

调用mServiceHandler,根据业务类型创建一个NotificationTransaction对象,如下代码:《TAG 2-2》

                case EVENT_TRANSACTION_REQUEST:

                    int serviceId = msg.arg1;

                    try {

                        TransactionBundle args = (TransactionBundle) msg.obj;

                        TransactionSettings transactionSettings;



                        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {

                            Log.v(TAG, "EVENT_TRANSACTION_REQUEST MmscUrl=" +

                                    args.getMmscUrl() + " proxy port: " + args.getProxyAddress());

                        }



                        // Set the connection settings for this transaction.

                        // If these have not been set in args, load the default settings.

                        String mmsc = args.getMmscUrl();

                        if (mmsc != null) {

                            transactionSettings = new TransactionSettings(

                                    mmsc, args.getProxyAddress(), args.getProxyPort());

                        } else {

                            transactionSettings = new TransactionSettings(

                                                    TransactionService.this, null);

                        }



                        int transactionType = args.getTransactionType();



                        if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {

                            Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" +

                                    transactionType + " " + decodeTransactionType(transactionType));

                            if (transactionSettings != null) {

                                Log.v(TAG, "mmsc=" + transactionSettings.getMmscUrl()

                                    + ", address=" + transactionSettings.getProxyAddress()

                                    + ", port=" + transactionSettings.getProxyPort());

                            }

                        }



                        // Create appropriate transaction

                        switch (transactionType) {

                            case Transaction.NOTIFICATION_TRANSACTION:

                                String uri = args.getUri();

                                if (uri != null) {

                                    transaction = new NotificationTransaction(

                                            TransactionService.this, serviceId,

                                            transactionSettings, uri);

                                } else {

                                    // Now it's only used for test purpose.

                                    byte[] pushData = args.getPushData();

                                    PduParser parser = new PduParser(pushData);

                                    GenericPdu ind = parser.parse();



                                    int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;

                                    if ((ind != null) && (ind.getMessageType() == type)) {

                                        transaction = new NotificationTransaction(

                                                TransactionService.this, serviceId,

                                                transactionSettings, (NotificationInd) ind);

                                    } else {

                                        Log.e(TAG, "Invalid PUSH data.");

                                        transaction = null;

                                        return;

                                    }

                                }

                                break;

                            case Transaction.RETRIEVE_TRANSACTION://这里当用户在界面中点击下载,会调用MessageListItem.java方法中的startDownloadAttachment()方法,随后会走这里。

                                transaction = new RetrieveTransaction(

                                        TransactionService.this, serviceId,

                                        transactionSettings, args.getUri());

                                break;


                            case Transaction.SEND_TRANSACTION:

                                transaction = new SendTransaction(

                                        TransactionService.this, serviceId,

                                        transactionSettings, args.getUri());

                                break;

                            case Transaction.READREC_TRANSACTION:

                                transaction = new ReadRecTransaction(

                                        TransactionService.this, serviceId,

                                        transactionSettings, args.getUri());

                                break;

                            default:

                                Log.w(TAG, "Invalid transaction type: " + serviceId);

                                transaction = null;

                                return;

                        }



                        if (!processTransaction(transaction)) {

                            transaction = null;

                            return;

                        }



                        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || DEBUG) {

                            Log.v(TAG, "Started processing of incoming message: " + msg);

                        }

                    } catch (Exception ex) {

                        Log.w(TAG, "Exception occurred while handling message: " + msg, ex);



                        if (transaction != null) {

                            try {

                                transaction.detach(TransactionService.this);

                                if (mProcessing.contains(transaction)) {

                                    synchronized (mProcessing) {

                                        mProcessing.remove(transaction);

                                    }

                                }

                            } catch (Throwable t) {

                                Log.e(TAG, "Unexpected Throwable.", t);

                            } finally {

                                // Set transaction to null to allow stopping the

                                // transaction service.

                                transaction = null;

                            }

                        }

                    } finally {

                        if (transaction == null) {

                            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {

                                Log.v(TAG, "Transaction was null. Stopping self: " + serviceId);

                            }



                            launchRetryAttempt++;

                            if (launchRetryAttempt <= maxLaunchRetryAttempts) {

                                Log.d(TAG, "launchTransaction retry attempt - "

                                        + launchRetryAttempt);

                                TransactionBundle args = (TransactionBundle) msg.obj;

                                sleep(5*1000);

                                launchTransaction(serviceId, args, false);

                            } else {

                                Log.e(TAG, "Multiple launchTransaction retries failed");

                                launchRetryAttempt = 0;

                                decRefCount();



                            }

                        }

                    }

                    return;

  这里接着调用processTransaction()方法;

*/

        private boolean processTransaction(Transaction transaction) throws IOException {

            // Check if transaction already processing

            synchronized (mProcessing) {

                for (Transaction t : mPending) {

                    if (t.isEquivalent(transaction)) {

                        Log.d(TAG, "Transaction already pending: " +

                                    transaction.getServiceId());

                        decRefCount();

                        return true;

                    }

                }

                for (Transaction t : mProcessing) {

                    if (t.isEquivalent(transaction)) {

                        Log.d(TAG, "Duplicated transaction: " + transaction.getServiceId());

                        decRefCount();

                        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.

                */

                Log.d(TAG, "processTransaction: call beginMmsConnectivity...");



                int connectivityResult = beginMmsConnectivity();

                if (connectivityResult == PhoneConstants.APN_REQUEST_STARTED) {

                    mPending.add(transaction);

                    if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {

                        Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " +

                                "defer transaction pending MMS connectivity");

                    }

                    return true;

                }



                Log.d(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.DEBUG) || DEBUG) {

                Log.v(TAG, "processTransaction: starting transaction " + transaction);

            }



            // Attach to transaction and process it

            transaction.attach(TransactionService.this);

            transaction.process();

            return true;

        }

    }

上述代码《TAG:2-3》中,调用Transaction.java类中的getPdu()方法下载彩信数据:

05-11 22:47