關於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規範的簡訊,檔案會有所差異。
這裡的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層後,首先由這個狀態機接收。
我們再來看一上這個狀態機長什麼樣:
當狀態機初始化好後,會停留在Idle狀態。當簡訊到來的時候,首先會由Idle狀態接收到,之後就會按以下流程進行處理
這兩張狀態圖怎麼看呢,我們先來看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狀態。這裡不再描述。大家往下跟一下程式碼就清楚了。