当前位置: 代码迷 >> Android >> 深度分析:Android4.3下MMS发送到附件替音频文件(音频为系统内置音频)的彩信给自己,添加音频-发送彩信-接收彩信-下载音频附件-预览-播放(二,发送彩信<2>)
  详细解决方案

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

热度:461   发布时间:2016-04-28 05:17:59.0
深度分析:Android4.3下MMS发送到附件为音频文件(音频为系统内置音频)的彩信给自己,添加音频-发送彩信-接收彩信-下载音频附件-预览-播放(二,发送彩信<2>)

由于前一篇已经介绍了启动TransactionService之前的主要内容,本篇主要介绍TransactionService处理彩信业务的主要逻辑流程。

TransactionService,与短信的SmsReceiverService类似,是负责处理彩信的服务,可以发送,接收等。对于TransactionService来讲,所有的需要处理的流程,无论是发送还是接收,都是一个Transaction。它内部有二个队列,一个是当前正在处理(processing)的Transaction,一个是待处理(pending)的Transaction。它维护这二个队列,并检查网络的连接,打开彩信网络连接,准备和检查环境,然后从待处理的队列中取出第一个,放入正在处理的队列中,并处理这个Transaction,也就是调用Transaction.process()。

发送彩信是一个SendTransaction,它的process()方法负责发送彩信,它会创建一个独立的线程来做,因此不会阻塞TransactionService,处理服务就可以再处理其他的Transaction。它会先从数据库中取出彩信Pdu,M-Send.req,(SendReq),更新一些字段,比如date,然后调用其父类Transaction.java中的方法sendPdu来把SendReq发送出去,sendPdu()会返回发送的结果(send confirmation)。Transaction.sendPdu()会先设置好网路,然后直接调用HttpUtils中的httpConnection()方法,用HTTP把彩信发送出去,同时取得返回消息(Response)给SendTransaction。SendTransaction会检查发送结果,返回结果(Send Confirmation),分析状态并更新至数据库(比如发送失败或发送成功)。UI会监听到状态变化,并更新信息列表。

首先,先简单介绍一下方法流程:

onCreate()

->onStartCommand()//使用handler发送消息【EVENT_NEW_INTENT】

->onNewIntent()

-> launchTransaction ()

-> sendMessage()【ServiceHandler.java<sub>】【EVENT_TRANSACTION_REQUEST】

->handlemessage ()【ServiceHandler.java<sub>】【EVENT_TRANSACTION_REQUEST】

-> new SendTransaction()【Transaction.SEND_TRANSACTION

- >processTransaction(transaction)

->process();【SendTransaction.java】<注意,标红处查看代码 getTransactionType     case PduHeaders.MESSAGE_TYPE_SEND_REQ:     return Transaction.SEND_TRANSACTION>

-> run()【SendTransaction.java】

-> sendPdu ()【Transcation.java】

-> HttpUtils.httpConnection()


这里从服务的生命周期函数onStartCommand()开始进行分析;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            Log.d(TAG, "onStartCommand(): E");
            incRefCount();

            Message msg = mServiceHandler.obtainMessage(EVENT_NEW_INTENT);
            msg.arg1 = startId;
            msg.obj = intent;
            mServiceHandler.sendMessage(msg);
        }
        return Service.START_NOT_STICKY;
    }

   @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "Handling incoming message: " + msg + " = " + decodeMessage(msg));

            Transaction transaction = null;

            switch (msg.what) {
                case EVENT_NEW_INTENT:
                    onNewIntent((Intent)msg.obj, msg.arg1);
                    break;
在上述方法中,使用handler发送了一个消息并在处理消息的方法中调用onNewIntent方法进行处理;
 public void onNewIntent(Intent intent, int serviceId) {
        //获得一个网络连接管理器,用来判断当前网络连接的状态
        mConnMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        /*AddBy:yabin.huang BugID:SWBUG00029243 Date:20140515*/
        if (mConnMgr == null) {
            endMmsConnectivity();
            decRefCount();
            return ;
        }
        NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
        boolean noNetwork = ni == null || !ni.isAvailable();

        Log.d(TAG, "onNewIntent: serviceId: " + serviceId + ": " + intent.getExtras() +
                " intent=" + intent);
        Log.d(TAG, "    networkAvailable=" + !noNetwork);

        Bundle extras = intent.getExtras();
        String action = intent.getAction();
        if ((ACTION_ONALARM.equals(action) || ACTION_ENABLE_AUTO_RETRIEVE.equals(action) ||
                (extras == null)) || ((extras != null) && !extras.containsKey("uri")
                && !extras.containsKey(CANCEL_URI))) {

            //We hit here when either the Retrymanager triggered us or there is
            //send operation in which case uri is not set. For rest of the
            //cases(MT MMS) we hit "else" case.

            // Scan database to find all pending operations.
            Cursor cursor = PduPersister.getPduPersister(this).getPendingMessages(
                    System.currentTimeMillis());
            Log.d(TAG, "Cursor= "+DatabaseUtils.dumpCursorToString(cursor));
            if (cursor != null) {
                try {
                    int count = cursor.getCount();

                    //if more than 1 records are present in DB.
                    if (count > 1) {
                        incRefCountN(count-1);
                        Log.d(TAG, "onNewIntent() multiple pending items mRef=" + mRef);
                    }

                    Log.d(TAG, "onNewIntent: cursor.count=" + count + " action=" + action);

                    if (count == 0) {
                        Log.d(TAG, "onNewIntent: no pending messages. Stopping service.");
                        RetryScheduler.setRetryAlarm(this);
                        cleanUpIfIdle(serviceId);
                        decRefCount();
                        return;
                    }

                    int columnIndexOfMsgId = cursor.getColumnIndexOrThrow(PendingMessages.MSG_ID);
                    int columnIndexOfMsgType = cursor.getColumnIndexOrThrow(
                            PendingMessages.MSG_TYPE);

                    while (cursor.moveToNext()) {
                        int msgType = cursor.getInt(columnIndexOfMsgType);
                        int transactionType = getTransactionType(msgType);
                        Log.d(TAG, "onNewIntent: msgType=" + msgType + " transactionType=" +
                                    transactionType);
                        if (noNetwork) {
                            onNetworkUnavailable(serviceId, transactionType);
                            Log.d(TAG, "No network during MO or retry operation");
                            decRefCountN(count);
                            Log.d(TAG, "Reverted mRef to =" + mRef);
                            return;
                        }
                        switch (transactionType) {
                            case -1:
                                decRefCount();
                                break;
                            case Transaction.RETRIEVE_TRANSACTION:
                                // If it's a transiently failed transaction,
                                // we should retry it in spite of current
                                // downloading mode. If the user just turned on the auto-retrieve
                                // option, we also retry those messages that don't have any errors.
                                int failureType = cursor.getInt(
                                        cursor.getColumnIndexOrThrow(
                                                PendingMessages.ERROR_TYPE));
                                DownloadManager downloadManager = DownloadManager.getInstance();
                                boolean autoDownload = downloadManager.isAuto();
                                boolean isMobileDataEnabled = mConnMgr.getMobileDataEnabled();
                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                                    Log.v(TAG, "onNewIntent: failureType=" + failureType +
                                            " action=" + action + " isTransientFailure:" +
                                            isTransientFailure(failureType) + " autoDownload=" +
                                            autoDownload);
                                }
                                if (!autoDownload || MessageUtils.isMmsMemoryFull()
                                        || !isMobileDataEnabled) {
                                    // If autodownload is turned off, don't process the
                                    // transaction.
                                    Log.d(TAG, "onNewIntent: skipping - autodownload off");
                                    decRefCount();
                                    break;
                                }
                                // Logic is twisty. If there's no failure or the failure
                                // is a non-permanent failure, we want to process the transaction.
                                // Otherwise, break out and skip processing this transaction.
                                if (!(failureType == MmsSms.NO_ERROR ||
                                        isTransientFailure(failureType))) {
                                    if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                                        Log.v(TAG, "onNewIntent: skipping - permanent error");
                                    }
                                    decRefCount();
                                    break;
                                }
                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                                    Log.v(TAG, "onNewIntent: falling through and processing");
                                }
                               // fall-through
                            default:
                                Uri uri = ContentUris.withAppendedId(
                                        Mms.CONTENT_URI,
                                        cursor.getLong(columnIndexOfMsgId));

                                String txnId = getTxnIdFromDb(uri);
                                int subId = getSubIdFromDb(uri);
                                Log.d(TAG, "SubId from DB= "+subId);

                                if(subId != MultiSimUtility.getCurrentDataSubscription
                                        (getApplicationContext())) {
                                    Log.d(TAG, "This MMS transaction can not be done"+
                                         "on current sub. Ignore it. uri="+uri);
                                    decRefCount();
                                    break;
                                }

                                int destSub = intent.getIntExtra(Mms.SUB_ID, -1);
                                int originSub = intent.getIntExtra(
                                        MultiSimUtility.ORIGIN_SUB_ID, -1);

                                Log.d(TAG, "Destination Sub = "+destSub);
                                Log.d(TAG, "Origin Sub = "+originSub);

                                addUnique(txnId, destSub, originSub);

                                TransactionBundle args = new TransactionBundle(
                                        transactionType, uri.toString());
                                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                                    Log.v(TAG, "onNewIntent: launchTransaction uri=" + uri);
                                }
                                // FIXME: We use the same serviceId for all MMs.
                                launchTransaction(serviceId, args, false);
                                break;
                        }
                    }
                } finally {
                    cursor.close();
                }
            } else {
                Log.d(TAG, "onNewIntent: no pending messages. Stopping service.");
                RetryScheduler.setRetryAlarm(this);
                cleanUpIfIdle(serviceId);
                decRefCount();
            }
        } else if ((extras != null) && extras.containsKey(CANCEL_URI)) {
            String uriStr = intent.getStringExtra(CANCEL_URI);
            Uri mCancelUri = Uri.parse(uriStr);
            for (Transaction transaction : mProcessing) {
                transaction.cancelTransaction(mCancelUri);
            }
            for (Transaction transaction : mPending) {
                transaction.cancelTransaction(mCancelUri);
            }
        } else {
            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || DEBUG) {
                Log.v(TAG, "onNewIntent: launch transaction...");
            }
            String uriStr = intent.getStringExtra("uri");
            int destSub = intent.getIntExtra(Mms.SUB_ID, -1);
            int originSub = intent.getIntExtra(MultiSimUtility.ORIGIN_SUB_ID, -1);

            Uri uri = Uri.parse(uriStr);
            int subId = getSubIdFromDb(uri);
            String txnId = getTxnIdFromDb(uri);

            if (txnId == null) {
                Log.d(TAG, "Transaction already over.");
                decRefCount();
                return;
            }

            Log.d(TAG, "SubId from DB= "+subId);
            Log.d(TAG, "Destination Sub = "+destSub);
            Log.d(TAG, "Origin Sub = "+originSub);

            if (noNetwork) {
                synchronized (mRef) {
                    Log.e(TAG, "No network during MT operation");
                    decRefCount();
                }
                return;
            }

            addUnique(txnId, destSub, originSub);

            // For launching NotificationTransaction and test purpose.
            TransactionBundle args = new TransactionBundle(intent.getExtras());
            launchTransaction(serviceId, args, noNetwork);
        }
    }

调用lunchTransaction()方法启动事务来发送消息:

                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());
                            }
                        }

                       ......

                       case Transaction.SEND_TRANSACTION:
                                transaction = new SendTransaction(
                                        TransactionService.this, serviceId,
                                        transactionSettings, args.getUri());
                                break;

                      ......

上述方法中提到了TransactionSettings,它是对于一个处理流程的相关配置信息,里面含有MMSC(Multimedia Message Service Center),Proxy和ProxyPort。这些信息,特别对于发送和接收来说是十分重要的。因为对于手机的信息,并不是手机直接把信息发送到接收人的手机上,而是直接发给服务中心,后面就是由服务中心再把信息发送给对应的接收人的手机上。对于彩信也是这样,HttpUtils通过HTTP协议把彩信发送给MMSC,它是一个URL地址,之后对于发送方来讲,彩信就发送完了,彩信服务中心(MMSC)会处理接下来的发送过程,服务中心是与手机运营相关的,它由运营商来提供。对于Mms发送彩信,是不会特意指定TransactionSettings的,也就是说它不会指定MMSC和Proxy,那么TransactionService就会用系统默认的MMSC,Proxy作为TranscationSetting,MMSC,Proxy和ProxyPort需要从Telephony数据库中查询出来,它们是与具体手机的APN设置和具体的运营商相关。所以,这里如果想要改变彩信的配置信息,只能更改APN系统设置来完成。

而短信的发送就不涉及SMSC(短信服务中心),因为Frameworks中的工具已经封装好了SmsManager提供了几个发送短信的方法,可能它会去处理SMSC相关的东西。

然后接着调用了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;
        }
    }

接着调用了SendTransaction.java类的process()方法和run()方法:

@Override
    public void process() {
        mThread = new Thread(this, "SendTransaction");
        mThread.start();
    }

    public void run() {
        try {
            RateController rateCtlr = RateController.getInstance();
            if (rateCtlr.isLimitSurpassed() && !rateCtlr.isAllowedByUser()) {
                Log.e(TAG, "Sending rate limit surpassed.");
                return;
            }

            // Load M-Send.req from outbox
            PduPersister persister = PduPersister.getPduPersister(mContext);
            SendReq sendReq = (SendReq) persister.load(mSendReqURI);

            // Update the 'date' field of the PDU right before sending it.
            long date = System.currentTimeMillis() / 1000L;
            sendReq.setDate(date);

            // Persist the new date value into database.
            ContentValues values = new ContentValues(1);
            values.put(Mms.DATE, date);
            SqliteWrapper.update(mContext, mContext.getContentResolver(),
                                 mSendReqURI, values, null, null);

            // fix bug 2100169: insert the 'from' address per spec
            String lineNumber;
            if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) {
                lineNumber = MessageUtils.getLocalNumber(
                        MultiSimUtility.getCurrentDataSubscription(mContext));
                Log.d(TAG, "lineNumber " + lineNumber);
            } else {
                lineNumber = MessageUtils.getLocalNumber();
            }

            if (!TextUtils.isEmpty(lineNumber)) {
                sendReq.setFrom(new EncodedStringValue(lineNumber));
            }

            // Pack M-Send.req, send it, retrieve confirmation data, and parse it
            long tokenKey = ContentUris.parseId(mSendReqURI);
            byte[] response = sendPdu(SendingProgressTokenManager.get(tokenKey),
                                      new PduComposer(mContext, sendReq).make());
            SendingProgressTokenManager.remove(tokenKey);

            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                String respStr = new String(response);
                Log.d(TAG, "[SendTransaction] run: send mms msg (" + mId + "), resp=" + respStr);
            }

            SendConf conf = (SendConf) new PduParser(response).parse();
            if (conf == null) {
                Log.e(TAG, "No M-Send.conf received.");
            }

            // Check whether the responding Transaction-ID is consistent
            // with the sent one.
            byte[] reqId = sendReq.getTransactionId();
            byte[] confId = conf.getTransactionId();
            if (!Arrays.equals(reqId, confId)) {
                Log.e(TAG, "Inconsistent Transaction-ID: req="
                        + new String(reqId) + ", conf=" + new String(confId));
                return;
            }

            // From now on, we won't save the whole M-Send.conf into
            // our database. Instead, we just save some interesting fields
            // into the related M-Send.req.
            values = new ContentValues(2);
            int respStatus = conf.getResponseStatus();
            values.put(Mms.RESPONSE_STATUS, respStatus);

            if (respStatus != PduHeaders.RESPONSE_STATUS_OK) {
                SqliteWrapper.update(mContext, mContext.getContentResolver(),
                                     mSendReqURI, values, null, null);
                Log.e(TAG, "Server returned an error code: " + respStatus);
                return;
            }

            String messageId = PduPersister.toIsoString(conf.getMessageId());
            values.put(Mms.MESSAGE_ID, messageId);
            SqliteWrapper.update(mContext, mContext.getContentResolver(),
                                 mSendReqURI, values, null, null);

            // Move M-Send.req from Outbox into Sent.
            Uri uri = persister.move(mSendReqURI, Sent.CONTENT_URI);

            mTransactionState.setState(TransactionState.SUCCESS);
            mTransactionState.setContentUri(uri);
        } catch (Throwable t) {
            Log.e(TAG, Log.getStackTraceString(t));
        } finally {
            if (mTransactionState.getState() != TransactionState.SUCCESS) {
                mTransactionState.setState(TransactionState.FAILED);
                mTransactionState.setContentUri(mSendReqURI);
                Log.e(TAG, "Delivery failed.");
            }
            notifyObservers();
        }
    }

接着调用了sendPdu()方法通过http协议将彩信发送给SMSC;

protected byte[] sendPdu(long token, byte[] pdu,
            String mmscUrl) throws IOException, MmsException {
        if (pdu == null) {
            throw new MmsException();
        }

        ensureRouteToHost(mmscUrl, mTransactionSettings);
        return HttpUtils.httpConnection(
                mContext, token,
                mmscUrl,
                pdu, HttpUtils.HTTP_POST_METHOD,
                mTransactionSettings.isProxySet(),
                mTransactionSettings.getProxyAddress(),
                mTransactionSettings.getProxyPort());
    }

总结,可以看出数据库在信息的发送过程中扮演了重要的角色,当信息离开编辑器后就马上写入了数据库,发送过程中的各个类都是先从数据库中加载信息,然后做相应处理,然后写回数据库或是更新状态,然后再交由下一个流程来处理。而所谓的Pending Message Queue其实没有相应的数据结构,它们都是数据库中的信息且状态是待发送而已。所以信息离开编辑器后就被写入了数据库,只不过状态一直在改变,从发送中到已发送,或发送失败,或如果Telephony服务不可用会仍处在待发送,但对于UI页面来讲可能没有那么多状态,它可能只显示发送中,已发送和发送失败。




  相关解决方案