Android平台 短信发送流程剖析(含编码)
Android平台 短信发送流程剖析(含编码)
本文对Android平台短信发送流程进行了走读和剖析,特别是编码部分,今天将流程整理出来,以便平时参考,也希望对大家有用!!!
先上图,下面2个图是用PPT画的,这里截图附上来:
流程图1:
流程图2:
发送流程编码解析:
从上图中的GsmSMSDispatcher的sendText开始分析
//GsmSMSDispatcher.java
/** {@inheritDoc} */
@Override
// ①入口
protected void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu ( //转②分析
scAddr, destAddr, text, (deliveryIntent != null));
HashMap map = SmsTrackerMapFactory(destAddr, scAddr, text, pdu);
SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent,
RadioTechnologyFamily.RADIO_TECH_3GPP);
sendRawPdu(tracker); //转I分析
}
②分析:
//SmsMessage.java
public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, String message,
boolean statusReportRequested) {
return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null); //转③分析
}
③分析
//SmsMessage.java
public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, String message,
boolean statusReportRequested, byte[] header) {
return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,ENCODING_UNKNOWN/*默认编码方式*/); //转④分析
}
④分析 编码核心函数
//SmsMessage.java
public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, String message,
boolean statusReportRequested, byte[] header, int encoding) {
// 3
// Perform null parameter checks.
if (message == null || destinationAddress == null) {
return null;
}
SubmitPdu ret = new SubmitPdu();
// MTI = SMS-SUBMIT, UDHI = header != null
byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
//MTI :bit 0 和 bit1 UDHI:bit6
// 0x01 = 0000 0001 0x40 = 0010 0000
ByteArrayOutputStream bo = getSubmitPduHead(
scAddress, destinationAddress, mtiByte,
statusReportRequested, ret); //转⑤分析
// User Data (and length) //TP-DCS 和TP-UDL
byte[] userData;
if (encoding == ENCODING_UNKNOWN) {
// First, try encoding it with the GSM alphabet
encoding = ENCODING_7BIT; //默认先采用ENCODING_7BIT编码模式
}
try {
if (encoding == ENCODING_7BIT) {
userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header);
//采用ENCODING_7BIT进行编码,若出现编码异常,函数会抛出异常:EncodeException,转至⑥处。编码成功转至⑦。stringToGsm7BitPackedWithHeader分析转⑧处
} else { //assume UCS-2
try {
userData = encodeUCS2(message, header);
} catch(UnsupportedEncodingException uex) {
Log.e(LOG_TAG,
"Implausible UnsupportedEncodingException ",
uex);
return null;
}
}
} catch (EncodeException ex) { //⑥ 7bit编码模式失败后,就采用UCS-2进行编码
// Encoding to the 7-bit alphabet failed. Let's see if we can
// send it as a UCS-2 encoded message
try {
userData = encodeUCS2(message, header);
encoding = ENCODING_16BIT;
} catch(UnsupportedEncodingException uex) {
Log.e(LOG_TAG,
"Implausible UnsupportedEncodingException ",
uex);
return null;
}
}
if (encoding == ENCODING_7BIT) { //⑦ 7bit编码成功
if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
// Message too long
return null;
}
// TP-Data-Coding-Scheme
// Default encoding, uncompressed
// To test writing messages to the SIM card, change this value 0x00
// to 0x12, which means "bits 1 and 0 contain message class, and the
// class is 2". Note that this takes effect for the sender. In other
// words, messages sent by the phone with this change will end up on
// the receiver's SIM card. You can then send messages to yourself
// (on a phone with this change) and they'll end up on the SIM card.
//0x12 = 0001 0010 未压缩,class2,存储到SIM卡
//0x00 = 0000 0000 未压缩,class0,GSM7bit编码
bo.write(0x00);
} else { // assume UCS-2
if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
// Message too long
return null;
}
// TP-Data-Coding-Scheme
// Class 3, UCS-2 encoding, uncompressed
bo.write(0x0b); //0x0b = 0000 1011 未压缩,class3,UCS-2编码
}
// (no TP-Validity-Period)
bo.write(userData, 0, userData.length);
ret.encodedMessage = bo.toByteArray();
return ret;
}
⑤分析
//SmsMessage.java
private static ByteArrayOutputStream getSubmitPduHead(
String scAddress, String destinationAddress, byte mtiByte,
boolean statusReportRequested, SubmitPdu ret)
//scAddress为短信中心号码,destinationAddress为目标地址号码
//mtiByte为MTI和UDHI 编码数据,见上面分析,statusReportRequested为状态报告 //ret 下面会写入数据到ret
{
ByteArrayOutputStream bo = new ByteArrayOutputStream(
MAX_USER_DATA_BYTES + 40);
// SMSC address with length octet, or 0
if (scAddress == null) {
ret.encodedScAddress = null;
} else {
ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
scAddress);
}
// TP-Message-Type-Indicator (and friends)
if (statusReportRequested) {
// Set TP-Status-Report-Request bit. //TP-SRR bit 5 ,0x20 = 0010 0000
mtiByte |= 0x20;
if (Config.LOGD) Log.d(LOG_TAG, "SMS status report requested");
}
bo.write(mtiByte);
// space for TP-Message-Reference //TP-MR
bo.write(0);
byte[] daBytes;
daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
// destination address length in BCD digits, ignoring TON byte and pad
// TODO Should be better.
bo.write((daBytes.length - 1) * 2
- ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
// destination address
bo.write(daBytes, 0, daBytes.length);
// TP-Protocol-Identifier //TP--PID
bo.write(0);
return bo;
}
⑧分析
//GsmAlphabet.java
public static byte[] stringToGsm7BitPackedWithHeader(String data, byte[] header)
throws EncodeException { //这里传进来的head为null
if (header == null || header.length == 0) {
return stringToGsm7BitPacked(data); //转⑨分析
}
int headerBits = (header.length + 1) * 8;
int headerSeptets = (headerBits + 6) / 7;
byte[] ret = stringToGsm7BitPacked(data, headerSeptets, true);
// Paste in the header
ret[1] = (byte)header.length;
System.arraycopy(header, 0, ret, 2, header.length);
return ret;
}
⑨分析
//GsmAlphabet.java
public static byte[] stringToGsm7BitPacked(String data)
throws EncodeException {
return stringToGsm7BitPacked(data, 0, true); //转⑩分析
}
⑩分析
//GsmAlphabet.java
public static byte[] stringToGsm7BitPacked(String data, int startingSeptetOffset,
boolean throwException) throws EncodeException {
int dataLen = data.length();
int septetCount = countGsmSeptets(data, throwException) + startingSeptetOffset;
// 当传入的字符串data中含有charToGsm, charToGsmExtended中没有的字符时(例如汉字),该函数会抛出异常,这样在调用处⑥会捕获该异常。然后会采用UCS-2方式进行编码。
if (septetCount > 255) {
throw new EncodeException("Payload cannot exceed 255 septets");
}
int byteCount = ((septetCount * 7) + 7) / 8;
byte[] ret = new byte[byteCount + 1]; // Include space for one byte length prefix.
for (int i = 0, septets = startingSeptetOffset, bitOffset = startingSeptetOffset * 7;
i < dataLen && septets < septetCount;
i++, bitOffset += 7) {
char c = data.charAt(i);
int v = GsmAlphabet.charToGsm(c, throwException);
if (v == GSM_EXTENDED_ESCAPE) {
v = GsmAlphabet.charToGsmExtended(c); // Lookup the extended char.
packSmsChar(ret, bitOffset, GSM_EXTENDED_ESCAPE);
bitOffset += 7;
septets++;
}
packSmsChar(ret, bitOffset, v);
septets++;
}
ret[0] = (byte) (septetCount); // Validated by check above.
return ret;
}
I分析:
//SMSDispatcher.java
protected void sendRawPdu(SmsTracker tracker) {
HashMap map = tracker.mData;
byte pdu[] = (byte[]) map.get("pdu");
PendingIntent sentIntent = tracker.mSentIntent;
if (mSmsSendDisabled) {
if (sentIntent != null) {
try {
sentIntent.send(RESULT_ERROR_NO_SERVICE);
} catch (CanceledException ex) {}
}
Log.d(TAG, "Device does not support sending sms.");
return;
}
if (pdu == null) {
if (sentIntent != null) {
try {
sentIntent.send(RESULT_ERROR_NULL_PDU);
} catch (CanceledException ex) {}
}
return;
}
int ss = mPhone.getServiceState().getState();
// if IMS not registered on data and voice is not available...
if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
handleNotInService(ss, tracker);
} else {
String appName = getAppNameByIntent(sentIntent);
if (mCounter.check(appName, SINGLE_PART_SMS)) {
sendSms(tracker); // 转II分析
} else {
sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
}
}
}
II分析:
//GsmSMSDispatcher.java
/** {@inheritDoc} */
@Override
protected void sendSms(SmsTracker tracker) {
HashMap<String, Object> map = tracker.mData;
byte smsc[] = (byte[]) map.get("smsc");
byte pdu[] = (byte[]) map.get("pdu");
Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
if (tracker.mRetryCount > 0 || !isIms()) {
// this is retry, use old method
mCm.sendSMS(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), reply);
} else {
mCm.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), reply);
}
}