Android 多卡数据管理系统完整架构
我来为你深入讲解 DSDS (Dual SIM Dual Standby) 和 DSDA (Dual SIM Dual Active) 设备上的数据管理机制。
1. 核心概念层
1.1 关键术语
| 概念 | 定义 | 说明 |
|---|---|---|
| DDS (Default Data Subscription) | 默认数据订阅 | 用户通常使用的数据 SIM,由用户或系统选择 |
| NIDS (Non-Default Data Subscription) | 非默认数据订阅 | 备用数据 SIM,平时不提供数据 |
| PDS (Preferred Data Subscription) | 首选数据订阅 | 当前实际使用的数据 SIM,可能与 DDS 不同 |
| Opportunistic Data | 机会性数据 | 特定场景(如 CBRS)下临时切换到的数据 SIM |
| mMaxDataAttachModemCount | 最大数据附着 Modem 数 | 同时允许附着 PS 的 Modem 个数(单卡=1,双卡可能=1 或 2) |
2. 核心组件架构
┌─────────────────────────────────────────────────────────────┐
│ ConnectivityService (Framework) │
│ - 处理 NetworkRequest │
│ - 选择最佳网络 │
└────────────────┬────────────────────────────────────────────┘
│ NetworkRequest (MMS、INTERNET 等)
▼
┌─────────────────────────────────────────────────────────────┐
│ PhoneSwitcher (Singleton) │
│ - 决定哪个 Modem 处理数据 │
│ - 向 Modem 发送 ALLOW_DATA/setPreferredData 命令 │
│ - 处理 DDS 切换、紧急通话、语音通话中数据切换 │
└────────────────┬────────────────────────────────────────────┘
│
┌────────┴────────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ RadioConfig │ │ MultiSimSetting │
│ │ │ Controller │
│ HAL 命令执行 │ │ 设置协调、RA引擎 │
└──────────────┘ └──────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Per-Phone DataNetworkController (x2 on dual SIM) │
│ - 创建和管理该 SIM 的所有数据网络 (DataNetwork) │
│ - 处理 APN、PDP 上下文 │
│ - 管理网络请求(NetworkRequest)匹配 │
└─────────────────────────────────────────────────────────────┘
3. PhoneSwitcher - 数据路由决策引擎
3.1 主要职责
// PhoneSwitcher 职责
public class PhoneSwitcher extends Handler {
// 用户设置的默认数据 SIM ID
protected int mPrimaryDataSubId;
// 自动选择的数据 SIM ID(CBRS、自动切换等)
private int mAutoSelectedDataSubId;
// 通话中的手机 ID(影响数据路由)
protected int mPhoneIdInVoiceCall;
// 最终的首选数据手机 ID(发送给 Modem)
protected int mPreferredDataPhoneId;
// 最终的首选数据订阅 ID
protected WatchedInt mPreferredDataSubId;
}
3.2 mPreferredDataPhoneId 的决策逻辑
updatePreferredDataPhoneId() 决策流程:
1️⃣ 紧急通话覆盖?
YES ➜ 使用 mEmergencyOverride.mPhoneId
(用于 GNSS SUPL 定位)
2️⃣ 有活跃语音通话?
YES ➜ IMS 注册技术 != IWLAN?
YES ➜ shouldSwitchDataDueToInCall()?
YES ➜ 使用通话手机的数据
NO ➜ 使用 getFallbackDataPhoneIdForInternetRequests()
NO ➜ 使用 IWLAN 上的通话, 不切换数据
3️⃣ 无活跃通话
➜ 使用 getFallbackDataPhoneIdForInternetRequests()
getFallbackDataPhoneIdForInternetRequests() 逻辑:
┌─ 有机会性数据 SIM (Opportunistic)?
│ YES ➜ 使用机会性 SIM
│ NO ➜ 使用主数据 SIM (mPrimaryDataSubId)
│
└─ 自动切换建议 (mAutoSelectedDataSubId)?
YES ➜ 用自动选择的
NO ➜ 用主数据 SIM
// 核心代码示例
protected void updatePreferredDataPhoneId() {
// 1. 检查紧急覆盖
if (mEmergencyOverride != null && findPhoneById(mEmergencyOverride.mPhoneId) != null) {
mPreferredDataPhoneId = mEmergencyOverride.mPhoneId;
return;
}
// 2. 检查语音通话
int imsRegTech = mImsRegTechProvider.get(mContext, mPhoneIdInVoiceCall);
if (isAnyVoiceCallActiveOnDevice() && imsRegTech != REGISTRATION_TECH_IWLAN) {
if (imsRegTech != REGISTRATION_TECH_CROSS_SIM) {
mPreferredDataPhoneId = shouldSwitchDataDueToInCall()
? mPhoneIdInVoiceCall
: getFallbackDataPhoneIdForInternetRequests();
} else {
logl("IMS call on cross-SIM, skip switching data");
}
} else {
// 3. 无通话
mPreferredDataPhoneId = getFallbackDataPhoneIdForInternetRequests();
}
// 更新订阅 ID
mPreferredDataSubId.set(SubscriptionManager.getSubscriptionId(mPreferredDataPhoneId));
}
4. 数据路由命令执行
4.1 两种 HAL 命令方案
方案 A: setDataAllowed(较旧)
// 适用于 mMaxDataAttachModemCount < mActiveModemCount 的情况
// 例如:双卡但只能有一个 Modem 附着 PS
private void sendRilCommands(int phoneId) {
if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA) {
if (mActiveModemCount > 1) {
// 对每个 Modem 分别发送 setDataAllowed 命令
PhoneFactory.getPhone(phoneId)
.mCi.setDataAllowed(isPhoneActive(phoneId), message);
}
}
}
// 逻辑:
// - 活跃手机: setDataAllowed(true) ➜ Modem 可以附着 PS
// - 非活跃手机: setDataAllowed(false) ➜ Modem 不能附着 PS
方案 B: setPreferredDataModem(新型)
// 适用于 mMaxDataAttachModemCount == mActiveModemCount 的情况
// 例如:双卡双活(DSDA)
private void sendRilCommands(int phoneId) {
if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
if (phoneId == mPreferredDataPhoneId) {
// 告诉 Modem 哪个逻辑 Modem 是首选数据 Modem
mRadioConfig.setPreferredDataModem(mPreferredDataPhoneId, message);
}
}
}
// 优点:
// - 所有 Modem 都可以附着 PS(DSDA 优势)
// - Modem 层知道哪个是"首选",可以优化资源分配
// - 减少切换次数(不需要 attach/detach 序列)
4.2 选择流程
private void updateHalCommandToUse() {
mHalCommandToUse = mRadioConfig.isSetPreferredDataCommandSupported()
? HAL_COMMAND_PREFERRED_DATA
: HAL_COMMAND_ALLOW_DATA;
}
5. 多卡数据管理的关键场景
场景 1: 用户改变默认数据 SIM
用户在设置中选择 SIM2 作为默认数据
↓
SubscriptionManager.setDefaultDataSubId(subId2)
↓
mPrimaryDataSubId = subId2
↓
SubscriptionManagerService.broadcastSubId(
ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED, subId2)
↓
PhoneSwitcher 收到广播
↓
evaluateIfImmediateDataSwitchIsNeeded()
↓
updatePreferredDataPhoneId() // 更新 mPreferredDataPhoneId 到 phoneId1
↓
sendRilCommands(phoneId1) // 向 Modem 发送新的数据路由命令
↓
实际数据流量转移到 SIM2
场景 2: MMS 在非 DDS 上发送(多卡数据特例)
应用请求 NET_CAPABILITY_MMS
↓
ConnectivityService 向 PhoneSwitcher 发送 NetworkRequest(MMS)
↓
TelephonyNetworkFactory 收到请求
↓
TelephonyNetworkFactory 为非 DDS SIM 的 MMS 创建专用网络
↓
如果非 DDS SIM 需要使用数据:
- PhoneSwitcher.onRequestNetwork(mmsRequest)
- 检查: mActiveModemCount > 1 && 是 MMS 请求?
- YES ➜ 将非 DDS Modem 也激活 (activate phoneId)
- 向非 DDS Modem 发送 setDataAllowed(true)
↓
MMS 可在非 DDS SIM 上建立 PDP 上下文
↓
MMS 发送完成或超时
↓
PhoneSwitcher.onReleaseNetwork(mmsRequest)
↓
取消激活非 DDS Modem: setDataAllowed(false)
↓
数据流量回到 DDS SIM
关键代码:
// PhoneSwitcher.onRequestNetwork()
private void onRequestNetwork(NetworkRequest networkRequest) {
TelephonyNetworkRequest req = new TelephonyNetworkRequest(networkRequest, ...);
if (!mNetworkRequestList.contains(req)) {
mNetworkRequestList.add(req);
onEvaluate(REQUESTS_CHANGED, "netRequest");
}
}
// 在 onEvaluate() 中:
if (mActiveModemCount > 1 && networkRequest.hasCapability(MMS)) {
// 临时激活非 DDS Modem 以处理 MMS
activate(nonDdsPhoneId);
}
场景 3: 语音通话中的数据 SIM 切换
用户在 SIM1 上接听电话(SIM1 非 DDS)
↓
Phone.getState() != IDLE
↓
PhoneSwitcher.onEvaluate()
↓
updatesIfPhoneInVoiceCallChanged() = true
↓
mPhoneIdInVoiceCall = 0 (SIM1 的 phoneId)
↓
updatePreferredDataPhoneId()
↓
shouldSwitchDataDueToInCall() 检查:
✓ 通话手机 (SIM1) 数据是否启用?
✓ DDS 手机 (SIM2) 数据是否启用?
↓
YES ➜ 切换数据到 SIM1 (通话手机)
NO ➜ 保留数据在 SIM2 (DDS)
// 为什么这样设计?
// 在 DSDS 设备中,同时只有一个 Modem 能处理数据
// 如果 SIM1 打电话,其 PS 就被占用
// 数据流量无法通过 SIM1,必须切到其他 SIM
// 此时如果用户启用了 SIM1 的数据,系统会临时
// 切换数据到 SIM1 以优化用户体验
场景 4: 自动数据切换(Auto Data Switch)
AutoDataSwitchController 监控信号质量和网络质量
↓
定期评估: evaluateAutoDataSwitch()
↓
对比 DDS 和 NIDS 的信号强度、网络类型
↓
分数评估 (Score Tolerance):
NIDS 分数 > DDS 分数 + ScoreTolerance?
↓
YES ➜ 建议切换到 NIDS
▼
检查稳定性:
信号状态保持稳定 > StabilityThreshold 毫秒?
↓
YES ➜ 开始网络验证 (Ping 测试)
▼
验证结果 ✓
↓
PhoneSwitcher.trySetOpportunisticDataSubscription(nidsSubId, needValidation=false)
↓
mAutoSelectedDataSubId = nidsSubId
↓
updatePreferredDataPhoneId() // 现在用 NIDS 作为数据 SIM
↓
sendRilCommands() ➜ 切换到 NIDS
↓
数据通过更好的 NIDS 传输
// DDS 恢复时的回退:
当 DDS 信号改善或 NIDS 信号恶化 > Tolerance
↓
Auto switch 建议切回 DDS
↓
PhoneSwitcher.trySetOpportunisticDataSubscription(
DEFAULT_SUBSCRIPTION_ID) // 取消机会性设置
↓
mAutoSelectedDataSubId = DEFAULT_SUBSCRIPTION_ID
↓
switchback to DDS
6. 多 Modem 激活/禁用逻辑
6.1 使用 HAL_COMMAND_ALLOW_DATA 时
活动 Modem 管理:
┌─────────────────────────────────────┐
│ 初始状态 (DSDS 设备) │
│ mActiveModemCount = 2 │
│ mMaxDataAttachModemCount = 1 │
│ SIM1 (DDS), SIM2 (NIDS) │
└─────────────────────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
Phone 0 (SIM1) Phone 1 (SIM2)
active=true active=false
║ ║
║ setDataAllowed(1) ║ setDataAllowed(0)
║ ║
▼ ▼
Modem 0 Modem 1
允许 PS 附着 禁止 PS 附着
可建立 PDP 上下文 无法建立 PDP 上下文
场景 1: MMS 在 SIM2 上请求
↓
激活 Phone 1
↓
active=true
↓
setDataAllowed(1) 发送到 Modem 1
↓
Modem 1 也可以 PS 附着
↓
SIM2 可以建立 PDP 上下文
↓
MMS 完成
↓
停用 Phone 1
↓
setDataAllowed(0) 发送到 Modem 1
6.2 使用 HAL_COMMAND_PREFERRED_DATA 时(DSDA)
┌─────────────────────────────────────┐
│ DSDA 设备状态 │
│ mActiveModemCount = 2 │
│ mMaxDataAttachModemCount = 2 │
│ 所有 Modem 都可以 PS 附着 │
└─────────────────────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
Phone 0 (SIM1) Phone 1 (SIM2)
active=true active=true
║ ║
║ (无需 setDataAllowed) ║ (无需 setDataAllowed)
║ ║
▼ setPreferredDataModem(0)
Modem 0 = 首选 Modem 1 = 普通
优先处理 Internet 可处理其他 capability
可建立 PDP 上下文 可建立 PDP 上下文
优势:
- 两个 Modem 都可以建立 PDP
- Internet 流量优先用 Modem 0
- 无需频繁 attach/detach
- MMS 可以直接在 Modem 1 上建立
- 支持真正的双活数据连接
7. DataNetworkController - Per-SIM 数据网络管理
// 每个 SIM 有独立的 DataNetworkController 实例
public class DataNetworkController extends Handler {
// 此 Controller 关联的 SIM
private int mSubId;
// 此 SIM 上所有的数据网络(DataNetwork 实例)
// 例如: INTERNET, MMS, IMS, CBS 等
private List<DataNetwork> mDataNetworkList;
// 所有待满足的网络请求
private NetworkRequestList mAllNetworkRequestList;
// Internet 数据网络状态
private int mInternetDataNetworkState;
// 当前活跃的 Internet 数据网络集合
private Set<DataNetwork> mConnectedInternetNetworks;
}
7.1 DataNetworkController 的职责
网络请求 (NetworkRequest)
│
▼ addNetworkRequest()
╔═══════════════════════════════════════════════════════╗
║ DataNetworkController (Per-SIM) ║
║ ║
║ 1. 查找满足请求的 DataProfile ║
║ 2. 检查是否有现有网络可以满足此请求 ║
║ 3. 如果没有,创建新的 DataNetwork ║
║ 4. 将请求与 DataNetwork 关联 ║
║ 5. 监控 DataNetwork 的连接状态 ║
╚═══════════════════════════════════════════════════════╝
│
▼
DataNetwork (通信具体细节)
│
├─ 建立 PDP 上下文
├─ 等待连接
├─ 成功 ✓ / 失败 ✗
└─ 监控连接状态
▼
LinkProperties / NetworkCapabilities
│
▼
TelephonyNetworkFactory
│
▼
ConnectivityService (最终使用)
7.2 多卡场景下的 DataNetworkController 交互
场景: 用户有两个活跃 SIM,MMS 请求来自非 DDS SIM
ConnectivityService
│
├─ 要求 SIM1 (DDS) 的 DataNetworkController:
│ "我需要 INTERNET 数据"
│ ↓ 创建 Internet DataNetwork
│
└─ 要求 SIM2 (NIDS) 的 DataNetworkController:
"我需要 MMS 数据"
↓
但首先检查 PhoneSwitcher:
➜ "SIM2 的 Modem 被激活了吗?"
↓
PhoneSwitcher 说: "等等,我需要先激活 Modem2"
↓
setDataAllowed(true) 发送给 Modem2
↓
DataNetworkController2 现在可以创建 DataNetwork
↓
创建 MMS DataNetwork
↓
连接完成后:
MMS 通信通过 SIM2 的 PDP 上下文传输
场景结束:
MMS 请求结束
↓
PhoneSwitcher: "可以停用 Modem2 吗?"
↓
检查还有其他请求吗? NO
↓
setDataAllowed(false) 发送给 Modem2
↓
MMS DataNetwork 断开连接
8. 多卡数据管理的特殊功能
8.1 Cross-SIM IMS (Dual IMS)
用户有两个 IMS 注册:
- SIM1: 本地 IMS
- SIM2: 用 DDS (SIM1 的数据) 的 IMS
PhoneSwitcher:
│
├─ SIM1 通话 → 使用 SIM1 的数据
│
├─ SIM2 通话 (Cross-SIM) → 用 SIM1 的 Internet APN
│ └─ 不切换数据 SIM,而是通过 Cross-SIM IMS PDN 共享
│
└─ 两个同时通话? (DSDA)
├─ SIM1: 自己的数据
└─ SIM2: 通过 Cross-SIM IMS 共享 SIM1 的 Internet APN
8.2 紧急通话 DDS 切换
用户在 SIM2 (NIDS) 上拨打 911,但 SIM1 (DDS) 有 GPS 位置
PhoneSwitcher:
│
├─ Emergency call on SIM2?
│ ├─ 设备支持 Emergency SUPL on non-DDS?
│ │ ├─ YES: 不切换 DDS,直接用 SIM2 的数据
│ │ └─ NO: 需要切换 DDS 到 SIM2
│ │ ↓
│ │ mEmergencyOverride.mPhoneId = 1 (SIM2)
│ │ ↓
│ │ updatePreferredDataPhoneId()
│ │ ↓
│ │ sendRilCommands(1)
│ │ ↓
│ │ DDS 临时切换到 SIM2
│ │
│ └─ 通话结束?
│ ├─ 进入 ECBM?
│ │ └─ 等待 ECBM 退出或超时
│ │
│ └─ 恢复原 DDS
9. 多卡数据管理的配置参数
运营商配置 (CarrierConfig)
| 参数 | 含义 | 影响 |
|---|---|---|
KEY_AUTO_DATA_SWITCH_ALLOW_ROAMING_BOOL | 漫游时是否允许自动切换 | 是否在漫游状态切换数据 |
KEY_AUTO_DATA_SWITCH_SCORE_TOLERANCE_INT | 自动切换的信号分数容差 | 分数差 > 容差时切换 |
KEY_AUTO_DATA_SWITCH_AVAILABILITY_STABILITY_TIME_THRESHOLD_MILLIS | 稳定性时间 | 网络状态稳定多久后才切换 |
KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL | 是否支持 Cross-SIM IMS | NIDS 是否可通过 DDS 数据使用 IMS |
KEY_REQUIRE_PING_TEST_BEFORE_SWITCH_BOOL | 切换前是否需要 Ping 测试 | 网络验证策略 |
系统配置 (Framework Resource)
<!-- android/config.xml -->
<integer name="config_auto_data_switch_score_tolerance">10</integer>
<integer name="config_auto_data_switch_availability_stability_time_threshold_millis">2000</integer>
<bool name="config_auto_data_switch_allow_roaming">true</bool>
10. 完整数据路由决策树
┌─────────────────────────────────────────────────────────┐
│ 评估何时应该使用哪个 SIM 的数据 │
└─────────────────────────────────────────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
有紧急通话? 有语音通话?
│ │
YES│ YES│
▼ ▼
使用紧急 检查通话 SIM 是否
SIM 的数据 启用了数据?
(GNSS SUPL) │
├─ YES ➜ 切换数据到通话 SIM
│ (DSDS 限制)
└─ NO ➜ 保持原 DDS
NO│ NO│
▼ ▼
└─────────────┬─────────────┘
▼
有机会性数据 SIM?
(Opportunistic/CBRS)
│
YES│
▼
验证该 SIM 可用?
│
YES│
▼
使用机会性 SIM
NO│
▼
有自动切换建议?
(Auto Data Switch)
│
YES│
▼
检查信号/网络质量
分数 > DDS + 容差?
│
YES│
▼
验证稳定性
和网络连通性
│
YES│
▼
使用建议的 SIM
NO│
▼
┌───────────────┐
│ 使用 DDS │
│(最终默认) │
└───────────────┘
11. 故障排查点
| 问题 | 原因 | 检查点 |
|---|---|---|
| NIDS 无法建立数据连接 | Modem 未激活 | PhoneSwitcher 是否发送了 setDataAllowed(1) |
| MMS 在 NIDS 上失败 | mMaxDataAttachModemCount 限制 | 设备是 DSDS 还是 DSDA? |
| 自动切换不工作 | 配置禁用或分数计算错误 | CarrierConfig 中 auto switch 参数是否为正数? |
| 通话中数据中断 | 没有切换到通话 SIM | shouldSwitchDataDueToInCall() 返回值检查 |
| DDS 切换失败 | Modem 命令执行失败 | 检查 RadioConfig 响应状态 |
总结:多卡数据管理的核心设计理念
┌────────────────────────────────────────────────────────┐
│ 多卡数据管理的三层架构 │
│ │
│ 层 1: 决策层 (PhoneSwitcher) │
│ └─ 根据用户偏好、系统状态、网络质量 │
│ 决定使用哪个 SIM 的数据 │
│ │
│ 层 2: 执行层 (RadioConfig/MultiSimSettingController) │
│ └─ 将决策转化为 HAL 命令 │
│ (setDataAllowed / setPreferredDataModem) │
│ 或 Settings 更新 │
│ │
│ 层 3: 实现层 (DataNetworkController) │
│ └─ 每个 SIM 独立管理自己的 PDP 上下文 │
│ 根据 PhoneSwitcher 的指示创建/销毁 PDP │
│ │
└────────────────────────────────────────────────────────┘
核心原则:
- 单一责任: PhoneSwitcher 决策,RadioConfig 执行
- 隔离性: 每个 SIM 的 DataNetworkController 独立工作
- 优先级: 紧急 > 语音 > 用户偏好 > 自动切换 > 默认
- 可验证: 每次决策都有日志和指标记录