短彩信的收发流程

76 阅读9分钟

我来分析一下这个仓库中短信和彩信的收发流程。让我查看相关的代码:

现在让我整理一下短彩信的收发流程:


Android Telephony 短信和彩信收发流程详解 📱💬

基于 frameworks-opt-telephony 代码分析,短信(SMS)和彩信(MMS)有不同但相关的处理流程。


一、短信发送流程(SMS Outbound) 📤

流程架构图

应用层 (App)
    ↓ SmsManager.sendTextMessage()
SmsController (系统服务接口)
    ↓
SmsDispatchersController (多卡管理)
    ↓
SMSDispatcher (抽象类)
    ├─ GsmSMSDispatcher (GSM/UMTS/LTE)
    └─ CdmaSMSDispatcher (CDMA)
    ↓
运营商短信过滤器 (可选)
    ↓
Phone.mCi (CommandsInterface)
    ↓
RIL (Radio Interface Layer)
    ↓ HIDL/AIDL
HAL → Modem → SMSC (短信中心)

详细步骤

1. 应用发起短信请求

SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(
    destinationAddress,  // 目标号码
    scAddress,          // 短信中心号码(可为null)
    text,               // 短信内容
    sentIntent,         // 发送结果通知
    deliveryIntent      // 送达报告通知
);

2. SmsController 接收请求

  • 位置:SmsController.java
  • 实现 ISmsImplBase AIDL 接口
  • 职责:
    • 权限检查(SEND_SMS 权限)
    • 验证订阅 ID
    • 路由到对应的 Phone 实例
/**
 * Implements the ISmsImplBase interface used in the SmsManager API.
 */
public class SmsController extends ISmsImplBase {
    static final String LOG_TAG = "SmsController";

    private final Context mContext;
    private final PackageManager mPackageManager;
    private final int mVendorApiLevel;

    @NonNull private final FeatureFlags mFlags;

    @VisibleForTesting
    public SmsController(Context context, @NonNull FeatureFlags flags) {
        mContext = context;
        mFlags = flags;
        mPackageManager = context.getPackageManager();
        ServiceRegisterer smsServiceRegisterer = TelephonyFrameworkInitializer
                .getTelephonyServiceManager()
                .getSmsServiceRegisterer();
        if (smsServiceRegisterer.get() == null) {
            smsServiceRegisterer.register(this);
        }

        mVendorApiLevel = SystemProperties.getInt(
                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
    }

    private Phone getPhone(int subId) {
        Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
        if (phone == null) {
            phone = PhoneFactory.getDefaultPhone();
        }
        return phone;
    }

3. SmsDispatchersController 分发

  • 根据当前网络类型选择 Dispatcher:
    • GSM 网络 → GsmSMSDispatcher
    • CDMA 网络 → CdmaSMSDispatcher
    • IMS 网络 → ImsSmsDispatcher

4. SMSDispatcher 处理核心逻辑

public abstract class SMSDispatcher extends Handler {
    static final String TAG = "SMSDispatcher";    // accessed from inner class
    static final boolean DBG = false;
    private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
    private static final String MESSAGE_ID_EXTRA = "MessageId";
    protected static final String MAP_KEY_PDU = "pdu";
    protected static final String MAP_KEY_SMSC = "smsc";
    protected static final String MAP_KEY_DEST_ADDR = "destAddr";
    protected static final String MAP_KEY_SC_ADDR = "scAddr";
    protected static final String MAP_KEY_DEST_PORT = "destPort";
    protected static final String MAP_KEY_DATA = "data";
    protected static final String MAP_KEY_TEXT = "text";

    private static final int PREMIUM_RULE_USE_SIM = 1;
    private static final int PREMIUM_RULE_USE_NETWORK = 2;
    private static final int PREMIUM_RULE_USE_BOTH = 3;
    private final AtomicInteger mPremiumSmsRule = new AtomicInteger(PREMIUM_RULE_USE_SIM);
    private final SettingsObserver mSettingsObserver;

    /** SMS send complete. */
    protected static final int EVENT_SEND_SMS_COMPLETE = 2;

    /** Retry sending a previously failed SMS message */
    protected static final int EVENT_SEND_RETRY = 3;

    /** Confirmation required for sending a large number of messages. */
    private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4;

    /** Send the user confirmed SMS */
    static final int EVENT_SEND_CONFIRMED_SMS = 5; // accessed from inner class

    /** Don't send SMS (user did not confirm). */
    static final int EVENT_STOP_SENDING = 6; // accessed from inner class

    /** Don't send SMS for this app (User had already denied eariler.) */
    static final int EVENT_SENDING_NOT_ALLOWED = 7;

    /** Confirmation required for third-party apps sending to an SMS short code. */
    private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8;

    /** Confirmation required for third-party apps sending to an SMS short code. */
    private static final int EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE = 9;

关键检查:

  • 费用检查:检测是否发送到付费短号码
  • 使用限制SmsUsageMonitor 防止短信轰炸
  • 运营商过滤CarrierServicesSmsFilter 运营商定制处理
  • 权限确认:敏感操作需用户确认

5. 运营商短信服务包装(可选)

mCarrierMessagingServiceWrapper.sendTextSms(
    text,
    getSubId(),
    mTracker.mDestAddress,
    (mTracker.mDeliveryIntent != null)
        ? CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS
        : 0,

运营商可以通过 CarrierMessagingService 拦截和处理短信。

6. 编码和分片

  • 文本编码:
    • GSM 7-bit 编码(最多160字符)
    • UCS-2/UTF-16 编码(最多70字符)
  • 长短信自动分片(通过 SmsMessage.fragmentText()
  • 添加 UDH(User Data Header)用于拼接

7. 通过 RIL 发送到 Modem

  • GSM: mCi.sendSMS()RIL_REQUEST_SEND_SMS
  • CDMA: mCi.sendCdmaSms()RIL_REQUEST_CDMA_SEND_SMS
  • IMS: mCi.sendImsGsmSms() / sendImsCdmaSms()

8. Modem 处理

  • 构造 TPDU (Transfer Protocol Data Unit)
  • 通过无线信道发送到 SMSC(短信中心)
  • SMSC 转发到目标号码

9. 发送状态回调

  • EVENT_SEND_SMS_COMPLETE 事件
  • 通知应用 sentIntent
    • RESULT_OK: 发送成功
    • RESULT_ERROR_GENERIC_FAILURE: 通用失败
    • RESULT_ERROR_RADIO_OFF: 无线关闭
    • RESULT_ERROR_NO_SERVICE: 无服务

10. 送达报告(可选)

  • 如果请求了 deliveryIntent
  • Modem 收到网络的 Status Report
  • 通知应用短信已送达

二、短信接收流程(SMS Inbound) 📥

流程架构图

SMSC → Modem
    ↓ RIL_UNSOL_RESPONSE_NEW_SMS
RIL → Phone.mCi
    ↓
InboundSmsHandler (状态机)
    ├─ GsmInboundSmsHandler (GSM)
    └─ CdmaInboundSmsHandler (CDMA)
    ↓
存储到 raw 表 (SMS Provider)
    ↓
发送 ACK 到 Modem
    ↓
判断完整性 (单条/多条拼接)
    ↓
广播给应用 (Ordered Broadcast)
    ↓
默认短信应用处理
    ↓
从 raw 表删除

详细步骤

1. Modem 接收短信

  • 基站推送短信到设备
  • Modem 解码 TPDU
  • 通过 RIL 上报:RIL_UNSOL_RESPONSE_NEW_SMS

2. InboundSmsHandler 状态机处理

/**
 * This class broadcasts incoming SMS messages to interested apps after storing them in the
 * SmsProvider "raw" table and ACKing them to the SMSC. After each message has been broadcast, its
 * parts are removed from the raw table. If the device crashes after ACKing but before the broadcast
 * completes, the pending messages will be rebroadcast on the next boot.
 *
 * <p>The state machine starts in {@link IdleState} state. When we receive a new SMS from the radio,
 * the wakelock is acquired, then transition to {@link DeliveringState} state, where the message is
 * saved to the raw table, then acknowledged to the modem which in turn acknowledges it to the SMSC.
 *
 * <p>After saving the SMS, if the message is complete (either single-part or the final segment of a
 * multi-part SMS), we broadcast the completed PDUs as an ordered broadcast, then transition to
 * {@link WaitingState} state to wait for the broadcast to complete. When the local
 * {@link BroadcastReceiver} is called with the result, it sends {@link #EVENT_BROADCAST_COMPLETE}
 * to the state machine, causing us to either broadcast the next pending message (if one has arrived
 * while waiting for the broadcast to complete), or to transition back to the halted state after all
 * messages are processed. Then the wakelock is released and we wait for the next SMS.
 */

状态机状态:

  1. IdleState(空闲):等待新短信
  2. DeliveringState(投递中):保存、ACK、广播
  3. WaitingState(等待):等待应用处理完成

3. 保存到数据库

  • 写入 content://sms/raw
  • 多段短信保存所有分段
  • 包含:PDU、发送者、时间戳、引用编号等

4. 发送 ACK 给网络

  • 成功:mCi.acknowledgeLastIncomingGsmSms(true, 0, null)
  • 失败(如存储满):发送 NACK

关键点:先保存再 ACK,防止崩溃丢失短信

5. 多段短信拼接

  • 检查 UDH 中的引用编号、总段数、序号
  • 从数据库查询相同引用的其他分段
  • 所有分段到齐后组装完整消息

6. 广播给应用

  • Intent: SMS_DELIVER_ACTION / SMS_RECEIVED_ACTION
  • 有序广播:按优先级依次通知应用
    1. 运营商应用(最高优先级)
    2. 默认短信应用
    3. 其他监听应用
Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
intent.putExtra("pdus", pdus);
intent.putExtra("format", format);
context.sendOrderedBroadcast(intent, permission, ...);

7. 应用处理

  • 默认短信应用将短信写入 content://sms/inbox
  • 可以通过 setResultCode(RESULT_CANCELED) 阻止后续应用

8. 清理

  • 广播完成后从 raw 表删除
  • 状态机返回 IdleState
  • 释放 WakeLock

9. 特殊类型短信

  • Class 0 (Flash SMS):直接显示,不保存
  • Status Report:送达报告,更新已发短信状态
  • WAP Push:彩信通知,转给 WapPushOverSms 处理
  • SIM 短信:存储在 SIM 卡的短信

三、彩信发送流程(MMS Outbound) 📧

流程概览

彩信不通过 Telephony 框架直接发送,而是通过数据网络(HTTP):

彩信应用
    ↓
构造 MMS PDU (多媒体内容)
    ↓
请求 MMS 数据连接
TelephonyNetworkFactory
    ↓ NET_CAPABILITY_MMS
DataNetworkController
    ↓
建立 MMS APN 数据连接
    ↓
通过 HTTP POST 上传到 MMSC
    ↓
MMSC 发送 WAP Push 通知给接收方

详细步骤

1. 彩信应用准备

  • 编码彩信内容(文本、图片、音频、视频)
  • 构造 MMS PDU(使用 com.google.android.mms 库)
  • 从 APN 配置读取 MMSC URL、代理地址

2. 请求 MMS 专用网络

NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
    .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
    .build();
    
connectivityManager.requestNetwork(request, callback);

3. 建立 MMS APN 连接

  • TelephonyNetworkFactory 识别 MMS 请求
  • DataNetworkController 选择 MMS 类型的 DataProfile
  • 建立专用的 PDN 连接(即使主数据关闭也可用)

4. HTTP 上传

  • 通过 MMS APN 的网络接口
  • POST 请求到 MMSC(如 http://mmsc.provider.com/mms
  • 可能需要通过代理服务器

5. MMSC 处理

  • 存储彩信内容
  • 向接收方发送 WAP Push 通知(SMS DATA)

四、彩信接收流程(MMS Inbound) 📬

流程架构图

MMSC 发送 WAP Push 通知 (SMS DATA)
    ↓
Modem → RIL → InboundSmsHandler
    ↓
识别为 WAP Push (端口 2948/2949)
    ↓
WapPushOverSms.dispatchWapPdu()
    ↓
解析 MMS 通知 (NotificationInd)
    ↓ 
广播给彩信应用
    ↓
彩信应用请求 MMS 网络
    ↓
HTTP GET 下载完整彩信
    ↓
显示给用户

详细步骤

1. 接收 WAP Push 通知

  • 作为特殊的 SMS DATA 消息接收
  • 目标端口:2948(未加密)或 2949(加密)
  • InboundSmsHandler 识别 WAP Push

2. WAP Push 解析

// Continue if PDU parsing fails: the default messaging app may successfully parse the
// same PDU.
GenericPdu parsedPdu = null;
try {
    parsedPdu = new PduParser(intentData, shouldParseContentDisposition(subId)).parse();
} catch (Exception e) {
    Rlog.e(TAG, "Unable to parse PDU: " + e.toString());
}

if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) {
    final NotificationInd nInd = (NotificationInd) parsedPdu;
    // save the WAP push message size so that if a download request is made for it
    // while on a satellite connection we can check if the size is under the threshold
    WapPushCache.putWapMessageSize(
            nInd.getContentLocation(),
            nInd.getTransactionId(),
            nInd.getMessageSize()
    );
    if (nInd.getFrom() != null
            && BlockChecker.isBlocked(mContext, nInd.getFrom().getString(), null)) {
        result.statusCode = Intents.RESULT_SMS_HANDLED;
        return result;
    }
}

/**
 * Seek for application ID field in WSP header.
 * If application ID is found, WapPushManager substitute the message
 * processing. Since WapPushManager is optional module, if WapPushManager
 * is not found, legacy message processing will be continued.
 */
if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) {
    index = (int) pduDecoder.getValue32();
    pduDecoder.decodeXWapApplicationId(index);
    String wapAppId = pduDecoder.getValueString();
    if (wapAppId == null) {
        wapAppId = Integer.toString((int) pduDecoder.getValue32());
    }
    result.wapAppId = wapAppId;
    String contentType = ((mimeType == null) ?
            Long.toString(binaryContentType) : mimeType);
    result.contentType = contentType;
    if (DBG) Rlog.v(TAG, "appid found: " + wapAppId + ":" + contentType);
}

result.subId = subId;
result.phoneId = phoneId;
result.parsedPdu = parsedPdu;
result.mimeType = mimeType;
result.transactionId = transactionId;
result.pduType = pduType;
result.header = header;
result.intentData = intentData;
result.contentTypeParameters = pduDecoder.getContentParameters();
result.statusCode = Activity.RESULT_OK;

MMS 通知包含:

  • Content-Location: 彩信下载 URL
  • Message-Size: 彩信大小
  • Expiry: 过期时间
  • From: 发件人
  • Subject: 主题

3. 广播给彩信应用

Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
intent.setType("application/vnd.wap.mms-message");
intent.putExtra("data", pdu);
intent.putExtra("transactionId", transactionId);
context.sendOrderedBroadcast(intent, permission, ...);

4. 彩信应用下载

  • 请求 MMS 网络连接
  • 通过 HTTP GET 从 Content-Location 下载
  • 下载完整的 MMS PDU

5. 解析和显示

  • 解析 M-Retrieve.conf PDU
  • 提取多媒体附件(图片、音频、视频)
  • 保存到 content://mms 数据库
  • 显示给用户

6. 发送确认

  • HTTP POST M-NotifyResp.ind 到 MMSC
  • 通知 MMSC 彩信已接收

关键差异对比

特性SMS 短信MMS 彩信
传输方式信令通道(Control Plane)数据通道(User Plane / HTTP)
大小限制160字符(GSM 7-bit)或70字符(UCS-2)几百KB到几MB(运营商限制)
内容类型纯文本文本、图片、音频、视频
费用按条计费按数据量计费(或包月)
送达方式直接推送通知→下载两步
网络要求2G及以上需要数据连接
接口RIL 命令TCP/IP + HTTP
离线接收Modem 缓存MMSC 缓存

相关数据库表

SMS 数据库

  • content://sms/raw: 原始接收队列
  • content://sms/inbox: 收件箱
  • content://sms/sent: 已发送
  • content://sms/outbox: 发送队列
  • content://sms/failed: 发送失败

MMS 数据库

  • content://mms: 彩信消息
  • content://mms/part: 彩信附件

关键类总结

发送相关

  • SmsController: 系统服务入口
  • SMSDispatcher: 短信发送核心抽象类
  • GsmSMSDispatcher / CdmaSMSDispatcher: 协议实现
  • SmsUsageMonitor: 使用限制监控

接收相关

  • InboundSmsHandler: 接收处理状态机
  • GsmInboundSmsHandler / CdmaInboundSmsHandler: 协议实现
  • InboundSmsTracker: 追踪接收消息

彩信相关

  • WapPushOverSms: WAP Push 处理(彩信通知)
  • WspTypeDecoder: WSP 协议解码
  • DataNetworkController: MMS 数据连接管理

这就是 Android Telephony 中完整的短彩信收发流程!整个系统设计考虑了可靠性(先存储后ACK)、性能(状态机异步处理)和兼容性(支持多种协议)。