• 深度分析: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页面来讲可能没有那么多状态,它可能只显示发送中,已发送和发送失败。




  • 相关阅读:
    mysql高可用架构的构想
    shell进阶——expect免交互工具的使用
    Mysql性能优化之参数配置(转)
    mysql主从同步问题梳理
    使用mysql-proxy实现mysql的读写分离
    Mysql数据库的主从与主主
    Mariadb远程登陆配置及相关问题排查
    redis集群搭建及常用操作
    weblogic的linux静默搭建
    Python traceback 模块,追踪错误
  • 原文地址:https://www.cnblogs.com/bill-technology/p/4130922.html
Copyright © 2020-2023  润新知