天天看點

關于android 4.4短信(sms)接收流程-狀态機篇

google從4.4版本開始,為了解決重複接收多條短信問題,在短信接收的架構層中增加了一個狀态機專門用來接收短信。
           

首先什麼是狀态機,這裡不多說,網上已經有很多相關的文章,這邊引用一個:http://blog.csdn.net/pi9nc/article/details/27503071。如果想直接看源碼了解的。可以看StateMachine.java (frameworks\base\core\java\com\android\internal\util)

這裡我分幾部分來講短信接收過程中的狀态機,

一,涉及到的源檔案:

GsmInboundSmsHandler.java (frameworks\opt\telephony\src\java\com\android\internal\telephony\gsm)

InboundSmsHandler.java (frameworks\opt\telephony\src\java\com\android\internal\telephony)

InboundSmsTracker.java (frameworks\opt\telephony\src\java\com\android\internal\telephony)

針對于3gpp規範的短信,基本上就是以上兩支檔案。對于3gpp2規範的短信,檔案會有所差異。

關于android 4.4短信(sms)接收流程-狀态機篇

這裡的CdmaInboundSmsHandler即為3gpp2規範短信的内容。這次不會涉及這個class.

二,短信接收流程,

1,在GsmInboundSmsHandler的構造方法裡:

phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);
           

這一句很關鍵,這是在告訴RILJ,當有新短信來的時候,RILJ會把短信發給GsmInboundSmsHandler,實際上是由其父類處理。也就是InboundSmsHandler.

2,我們看一下InboundSmsHandler到底是什麼:

public abstract class InboundSmsHandler extends StateMachine {
           

這下清楚了。這就是接收短信的狀态機。短信到達framework層後,首先由這個狀态機接收。

我們再來看一上這個狀态機長什麼樣:

關于android 4.4短信(sms)接收流程-狀态機篇

當狀态機初始化好後,會停留在Idle狀态。當短信到來的時候,首先會由Idle狀态接收到,之後就會按以下流程進行處理

關于android 4.4短信(sms)接收流程-狀态機篇

這兩張狀态圖怎麼看呢,我們先來看startup狀态的源碼:

 class StartupState extends State {
        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
                case EVENT_NEW_SMS:
                case EVENT_BROADCAST_SMS:
                    deferMessage(msg);
                    return HANDLED;


                case EVENT_START_ACCEPTING_SMS:
                    transitionTo(mIdleState);
                    return HANDLED;
           

當SmsBroadcastUndelivered處理好table後,會發出EVENT_START_ACCEPTING_SMS.這時候,會從StartupState狀态切到Idle狀态。隻有在Idle狀态,才可以處理短信。

回到前面,我們最初在GsmInboundSmsHandler的構造方法裡:

phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);
           

也就是說當RILJ有短信的時候,會向GsmInboundSmsHandler發送EVENT_NEW_SMS,

現在這裡講的就是在Idle狀态下收到了EVENT_NEW_SMS。我們接着看

class IdleState extends State {
        @Override
        public void enter() {
            if (DBG) log("entering Idle state");
            sendMessageDelayed(EVENT_RELEASE_WAKELOCK, WAKELOCK_TIMEOUT);
        }

        @Override
        public void exit() {
            mWakeLock.acquire();
            if (DBG) log("acquired wakelock, leaving Idle state");
        }

        @Override
        public boolean processMessage(Message msg) {
            if (DBG) log("Idle state processing message type " + msg.what);
            switch (msg.what) {
                case EVENT_NEW_SMS:
                case EVENT_BROADCAST_SMS:
                    deferMessage(msg);
                    transitionTo(mDeliveringState);
                    return HANDLED;
           

在Idle狀态收到後event_new_sms後,接着将狀态切到DeliveringState.

class DeliveringState extends State {
        @Override
        public void enter() {
            if (DBG) log("entering Delivering state");
        }

        @Override
        public void exit() {
            if (DBG) log("leaving Delivering state");
        }

        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
                case EVENT_NEW_SMS:
                    // handle new SMS from RIL
                    handleNewSms((AsyncResult) msg.obj);
                    sendMessage(EVENT_RETURN_TO_IDLE);
                    return HANDLED;
           

在DeliveringState狀态下,終于看到處理短信的方法了:handleNewSms:

void handleNewSms(AsyncResult ar) {
        if (ar.exception != null) {
            loge("Exception processing incoming SMS: " + ar.exception);
            return;
        }

        int result;
        try {
            SmsMessage sms = (SmsMessage) ar.result;//首先把短信讀出來
            result = dispatchMessage(sms.mWrappedSmsMessage);調用<span style="font-family: Arial, Helvetica, sans-serif;">dispatchMessage</span>
        } catch (RuntimeException ex) {
            loge("Exception dispatching message", ex);
            result = Intents.RESULT_SMS_GENERIC_ERROR;
        }

        // RESULT_OK means that the SMS will be acknowledged by special handling,
        // e.g. for SMS-PP data download. Any other result, we should ack here.
        if (result != Activity.RESULT_OK) {
            boolean handled = (result == Intents.RESULT_SMS_HANDLED);
            notifyAndAcknowledgeLastIncomingSms(handled, result, null);
        }
    }
           
public int dispatchMessage(SmsMessageBase smsb) {
        // If sms is null, there was a parsing error.
        if (smsb == null) {
            loge("dispatchSmsMessage: message is null");
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }

        if (mSmsReceiveDisabled) {
            // Device doesn't support receiving SMS,
            log("Received short message on device which doesn't support "
                    + "receiving SMS. Ignored.");
            return Intents.RESULT_SMS_HANDLED;
        }

        return dispatchMessageRadioSpecific(smsb);
    }
           

我們接着看dispatchMessageRadioSpecific:

注意,這個方法的實作在GsmInboundSmsHandler,不在是它的父類。

protected int dispatchMessageRadioSpecific(SmsMessageBase smsb) {
        SmsMessage sms = (SmsMessage) smsb;

        if (sms.isTypeZero()) {//判斷這個是不是typezero類型的短信。關于typezero。可以看規範<span style="font-family: Arial, Helvetica, sans-serif;">3GPP TS 23.040 ,進3gpp官網下載下傳該規範即可</span>
            // 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("Received short message type 0, Don't display or store it. Send Ack");
            return Intents.RESULT_SMS_HANDLED;
        }

        // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1.
        if (sms.isUsimDataDownload()) {
            UsimServiceTable ust = mPhone.getUsimServiceTable();
            return mDataDownloadHandler.handleUsimDataDownload(ust, sms);
        }

        boolean handled = false;
        if (sms.isMWISetMessage()) {//這裡wmi,有關語音信箱的短信。
            mPhone.setVoiceMessageWaiting(1, -1);  // line 1: unknown number of msgs waiting
            handled = sms.isMwiDontStore();
            if (DBG) log("Received voice mail indicator set SMS shouldStore=" + !handled);
        } else if (sms.isMWIClearMessage()) {
            mPhone.setVoiceMessageWaiting(1, 0);   // line 1: no msgs waiting
            handled = sms.isMwiDontStore();
            if (DBG) log("Received voice mail indicator clear SMS shouldStore=" + !handled);
        }
        if (handled) {
            return Intents.RESULT_SMS_HANDLED;
        }

        if (!mStorageMonitor.isStorageAvailable() &&
                sms.getMessageClass() != SmsConstants.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;
        }

        return dispatchNormalMessage(smsb);//看這裡
    }
           
protected int dispatchNormalMessage(SmsMessageBase sms) {
        SmsHeader smsHeader = sms.getUserDataHeader();
        InboundSmsTracker tracker;

        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
            // Message is not concatenated.
            int destPort = -1;
            if (smsHeader != null && smsHeader.portAddrs != null) {
                // The message was sent to a port.
                destPort = smsHeader.portAddrs.destPort;
                if (DBG) log("destination port: " + destPort);
            }

            tracker = new InboundSmsTracker(sms.getPdu(), sms.getTimestampMillis(), destPort,
                    is3gpp2(), false);
        } else {
            // Create a tracker for this message segment.
            SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
            SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs;
            int destPort = (portAddrs != null ? portAddrs.destPort : -1);

            tracker = new InboundSmsTracker(sms.getPdu(), sms.getTimestampMillis(), destPort,
                    is3gpp2(), sms.getOriginatingAddress(), concatRef.refNumber,
                    concatRef.seqNumber, concatRef.msgCount, false);
        }

        if (VDBG) log("created tracker: " + tracker);
        return addTrackerToRawTableAndSendMessage(tracker);
    }
           

InboundSmsTracker終于出場了。這裡不多說,就是把短信封裝到InboundSmsTracker。接着看addTrackerToRawTableAndSendMessage:

protected int addTrackerToRawTableAndSendMessage(InboundSmsTracker tracker) {
        switch(addTrackerToRawTable(tracker)) {
        case Intents.RESULT_SMS_HANDLED:
            sendMessage(EVENT_BROADCAST_SMS, tracker);
            return Intents.RESULT_SMS_HANDLED;

        case Intents.RESULT_SMS_DUPLICATED:
            return Intents.RESULT_SMS_HANDLED;

        case Intents.RESULT_SMS_GENERIC_ERROR:
        default:
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }
    }
           

這裡有個很重要的方法,千萬别漏掉addTrackerToRawTable。它是把短信放入raw表裡。什麼是raw表,打開短信資料庫看一下就懂了。

private int addTrackerToRawTable(InboundSmsTracker tracker) {
        if (tracker.getMessageCount() != 1) {
            // check for duplicate message segments
            Cursor cursor = null;
            try {
                // sequence numbers are 1-based except for CDMA WAP, which is 0-based
                int sequence = tracker.getSequenceNumber();

                // convert to strings for query
                String address = tracker.getAddress();
                String refNumber = Integer.toString(tracker.getReferenceNumber());
                String count = Integer.toString(tracker.getMessageCount());

                String seqNumber = Integer.toString(sequence);

                // set the delete selection args for multi-part message
                String[] deleteWhereArgs = {address, refNumber, count};
                tracker.setDeleteWhere(SELECT_BY_REFERENCE, deleteWhereArgs);

                // Check for duplicate message segments
                cursor = mResolver.query(sRawUri, PDU_PROJECTION,
                        "address=? AND reference_number=? AND count=? AND sequence=?",
                        new String[] {address, refNumber, count, seqNumber}, null);

                // moveToNext() returns false if no duplicates were found
                if (cursor.moveToNext()) {
                    loge("Discarding duplicate message segment, refNumber=" + refNumber
                            + " seqNumber=" + seqNumber);
                    String oldPduString = cursor.getString(PDU_COLUMN);
                    byte[] pdu = tracker.getPdu();
                    byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
                    if (!Arrays.equals(oldPdu, tracker.getPdu())) {
                        loge("Warning: dup message segment PDU of length " + pdu.length
                                + " is different from existing PDU of length " + oldPdu.length);
                    }
                    return Intents.RESULT_SMS_DUPLICATED;   // reject message
                }
                cursor.close();
            } catch (SQLException e) {
                loge("Can't access multipart SMS database", e);
                return Intents.RESULT_SMS_GENERIC_ERROR;    // reject message
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }

        ContentValues values = tracker.getContentValues();

        if (VDBG) log("adding content values to raw table: " + values.toString());
        Uri newUri = mResolver.insert(sRawUri, values);//儲存到raw表
        if (DBG) log("URI of new row -> " + newUri);

        try {
            long rowId = ContentUris.parseId(newUri);
            if (tracker.getMessageCount() == 1) {
                // set the delete selection args for single-part message
                tracker.setDeleteWhere(SELECT_BY_ID, new String[]{Long.toString(rowId)});
            }
            return Intents.RESULT_SMS_HANDLED;
        } catch (Exception e) {
            loge("error parsing URI for new row: " + newUri, e);
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }
    }
           

現在回到上一個方法:

case Intents.RESULT_SMS_HANDLED:
            sendMessage(EVENT_BROADCAST_SMS, tracker);
            return Intents.RESULT_SMS_HANDLED;
           

這裡是發出了EVENT_BROADCAST_SMS.

特别注意,剛才我們的狀态是DeliveringState。是以,現在我們回到這個狀态下看怎麼處理這個event:

case EVENT_BROADCAST_SMS:
                    // if any broadcasts were sent, transition to waiting state
                    if (processMessagePart((InboundSmsTracker) msg.obj)) {
                        transitionTo(mWaitingState);
                    }
                    return HANDLED;
           

調用了processMessagePart,同時把狀态切為mWaitingState

boolean processMessagePart(InboundSmsTracker tracker) {
        int messageCount = tracker.getMessageCount();
        byte[][] pdus;
        int destPort = tracker.getDestPort();

        if (messageCount == 1) {
            // single-part message
            pdus = new byte[][]{tracker.getPdu()};
        } else {
            // multi-part message
            Cursor cursor = null;
            try {
                // used by several query selection arguments
                String address = tracker.getAddress();
                String refNumber = Integer.toString(tracker.getReferenceNumber());
                String count = Integer.toString(tracker.getMessageCount());

                // query for all segments and broadcast message if we have all the parts
                String[] whereArgs = {address, refNumber, count};
                cursor = mResolver.query(sRawUri, PDU_SEQUENCE_PORT_PROJECTION,
                        SELECT_BY_REFERENCE, whereArgs, null);

                int cursorCount = cursor.getCount();
                if (cursorCount < messageCount) {
                    // Wait for the other message parts to arrive. It's also possible for the last
                    // segment to arrive before processing the EVENT_BROADCAST_SMS for one of the
                    // earlier segments. In that case, the broadcast will be sent as soon as all
                    // segments are in the table, and any later EVENT_BROADCAST_SMS messages will
                    // get a row count of 0 and return.
                    return false;
                }

                // All the parts are in place, deal with them
                pdus = new byte[messageCount][];
                while (cursor.moveToNext()) {
                    // subtract offset to convert sequence to 0-based array index
                    int index = cursor.getInt(SEQUENCE_COLUMN) - tracker.getIndexOffset();

                    pdus[index] = HexDump.hexStringToByteArray(cursor.getString(PDU_COLUMN));

                    // Read the destination port from the first segment (needed for CDMA WAP PDU).
                    // It's not a bad idea to prefer the port from the first segment in other cases.
                    if (index == 0 && !cursor.isNull(DESTINATION_PORT_COLUMN)) {
                        int port = cursor.getInt(DESTINATION_PORT_COLUMN);
                        // strip format flags and convert to real port number, or -1
                        port = InboundSmsTracker.getRealDestPort(port);
                        if (port != -1) {
                            destPort = port;
                        }
                    }
                }
            } catch (SQLException e) {
                loge("Can't access multipart SMS database", e);
                return false;
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }

        BroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);//建立了一個receiver,用于向app廣播完後。做掃尾處理。

        if (destPort == SmsHeader.PORT_WAP_PUSH) {//判斷這個短信是不是wap push.也就是端口短信。我們普通短信都不是wap push
            // Build up the data stream
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            for (byte[] pdu : pdus) {
                // 3GPP needs to extract the User Data from the PDU; 3GPP2 has already done this
                if (!tracker.is3gpp2()) {
                    SmsMessage msg = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP);
                    pdu = msg.getUserData();
                }
                output.write(pdu, 0, pdu.length);
            }
            int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver, this);
            if (DBG) log("dispatchWapPdu() returned " + result);
            // result is Activity.RESULT_OK if an ordered broadcast was sent
            return (result == Activity.RESULT_OK);
        }

        Intent intent;
        if (destPort == -1) {
            intent = new Intent(Intents.SMS_DELIVER_ACTION);

            // Direct the intent to only the default SMS app. If we can't find a default SMS app
            // then sent it to all broadcast receivers.
            ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true);
            if (componentName != null) {
                // Deliver SMS message only to this receiver
                intent.setComponent(componentName);
                log("Delivering SMS to: " + componentName.getPackageName() +
                        " " + componentName.getClassName());
            }
        } else {
            Uri uri = Uri.parse("sms://localhost:" + destPort);
            intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
        }

        intent.putExtra("pdus", pdus);
        intent.putExtra("format", tracker.getFormat());
        dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
                AppOpsManager.OP_RECEIVE_SMS, resultReceiver);//将短信廣播出去。注意,在4.4版本裡,這裡隻是廣播給系統預設的default sms app.其它sms app是收不到短信的。
        return true;
    }
           

這個方法發送的短信廣播隻是系統預設的default sms app.其它sms app是收不到短信的。那麼其它接收短信的app.靠什麼收到短信呢。别急,我們剛才有講過

BroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);//建立了一個receiver,用于向app廣播完後。做掃尾處理。
           

我們來看一下這個receiver:

private final class SmsBroadcastReceiver extends BroadcastReceiver {
        private final String mDeleteWhere;
        private final String[] mDeleteWhereArgs;
        private long mBroadcastTimeNano;

        SmsBroadcastReceiver(InboundSmsTracker tracker) {
            mDeleteWhere = tracker.getDeleteWhere();
            mDeleteWhereArgs = tracker.getDeleteWhereArgs();
            mBroadcastTimeNano = System.nanoTime();
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(Intents.SMS_DELIVER_ACTION)) {
                // Now dispatch the notification only intent
                intent.setAction(Intents.SMS_RECEIVED_ACTION);
                intent.setComponent(null);
                dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
                        AppOpsManager.OP_RECEIVE_SMS, this);
            } else if
           

這裡再次廣播了一個intent.隻要第三方app去接收這個intent。就能收到短信了。

接下來就是把狀态機切回idle狀态。這裡不再描述。大家往下跟一下代碼就清楚了。