一、数据卡死检测机制
1. 检测层次结构
ConnectivityService (网络连接服务)
↓
NetworkMonitor (网络监控)
↓ (检测方法)
├─ DNS Events (DNS超时检测)
├─ TCP Metrics (TCP失败率检测)
└─ IPv4/IPv6 探针 (ICMP Reachability)
↓
数据卡死判定
↓
DataStallRecoveryManager (DSRM - 数据卡死恢复管理器)
2. 数据卡死的定义
根据代码注释,数据卡死是指:
- DNS 事件:连续多个 DNS 超时
- TCP 指标:TCP 包失败率超过阈值
- 网络探针失败:无法到达任何网关/DNS 服务器
这些指标由 NetworkMonitor 实时监测,当条件触发时,通过以下路径上报:
NetworkMonitor
→ ConnectivityService.notifyDataStallSuspected()
→ DataStallRecoveryManager.onInternetValidationStatusChanged(NOT_VALID)
二、四级递进恢复机制详解
恢复机制概览图
┌─────────────────────────────────────────────────────────────────┐
│ 数据卡死被检测到 (VALIDATION_STATUS_NOT_VALID) │
└────────────────┬──────────────────────────────────────────────────┘
│
↓
┌────────────────────┐
│ Level 0 │
│ GET_DATA_CALL_LIST │ ← 仅查询,不修改
└────────┬───────────┘
│ (等待 180s,检查验证状态)
↓
┌────────────────────┐
│ Level 1 │
│ CLEANUP │ ← 轻量级恢复,不涉及 Radio
│(DEACTIVATE/SETUP) │
└────────┬───────────┘
│ (等待 180s,检查验证状态)
↓
┌────────────────────┐
│ Level 2 │
│ RADIO_RESTART │ ← 中等级恢复,涉及 Radio Power 重启
└────────┬───────────┘
│ (等待 180s,检查验证状态)
↓
┌────────────────────┐
│ Level 3 │
│ RESET_MODEM │ ← 最终恢复,涉及 Modem 重启
│(NV_RESET_CONFIG) │
└────────┬───────────┘
│
↓
验证通过或已尝试所有步骤 → 复位到初始状态
2.1 Level 0: GET_DATA_CALL_LIST
目的:查询当前数据连接列表,同步 RIL 和 Framework 的数据连接状态
实现:
720| /** Recovery Action: RECOVERY_ACTION_GET_DATA_CALL_LIST */
721| private void getDataCallList() {
722| log("getDataCallList: request data call list");
723| mWwanDataServiceManager.requestDataCallList(null);
724| }
调用链:
DataStallRecoveryManager.getDataCallList()- →
DataServiceManager.requestDataCallList(Message) - →
IDataService.requestDataCallList(int phoneId, IDataServiceCallback callback)(AIDL) - → Modem → RIL 返回
DataCallResponse[]
使用场景:
- 当 Framework 认为数据连接出现问题,但 Modem 端可能正常
- 通过 RIL_REQUEST_GET_DATA_CALL_LIST 查询实际连接状态
- 可能发现 IP 地址或 DNS 服务器信息在 Modem 端已更新,但未上报给 Framework
关键配置:
- 延迟:CarrierConfig
KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY[0](默认 180000ms = 3分钟) - 可跳过:CarrierConfig
KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_ARRAY[0]
2.2 Level 1: CLEANUP
目的:完全断开现有数据连接,随后重新建立连接(Channel 重建)
实现:
726| /** Recovery Action: RECOVERY_ACTION_CLEANUP */
727| private void cleanUpDataNetwork() {
728| log("cleanUpDataNetwork: notify clean up data network");
729| mDataStallRecoveryManagerCallback.invokeFromExecutor(
730| mDataStallRecoveryManagerCallback::onDataStallReestablishInternet);
731| }
调用链:
DataStallRecoveryManager.cleanUpDataNetwork()
→ DataNetworkController.onDataStallReestablishInternet()
→ mDataNetworkList.stream()
.filter(DataNetwork::isInternetSupported)
.forEach(dataNetwork →
dataNetwork.tearDown(TEAR_DOWN_REASON_DATA_STALL)
)
→ RIL_REQUEST_DEACTIVATE_DATA_CALL (所有 PDN)
→ 断开连接后,等待网络请求重新建立
→ RIL_REQUEST_SETUP_DATA_CALL
具体步骤:
┌─ 1. 断开所有互联网数据连接 (DEACTIVATE_DATA_CALL)
│ ├─ 所有 isInternetSupported() == true 的 DataNetwork
│ ├─ 发送 RIL_REQUEST_DEACTIVATE_DATA_CALL for each CID
│ └─ 等待 Modem 回复 DEACTIVATE 响应
│
├─ 2. 断开完成后,unsatisfied network requests 重新评估
│ └─ DataNetworkController 检查是否有待满足的网络请求
│
└─ 3. 根据网络请求重新建立连接 (SETUP_DATA_CALL)
├─ 发送 RIL_REQUEST_SETUP_DATA_CALL
├─ 建立新的 PDN
└─ 获取新的 IP、DNS 等链接属性
核心代码:
3451| log("onDataStallReestablishInternet: Tear down data networks that support internet.");
3452| // 断开所有支持互联网的数据网络
3453| mDataNetworkList.stream()
3454| .filter(DataNetwork::isInternetSupported)
3455| .forEach(dataNetwork -> dataNetwork.tearDown(
3456| DataNetwork.TEAR_DOWN_REASON_DATA_STALL));
3457| }
为什么有效:
- 断开-重建可以清除 Modem 与 Radio 之间的沟通错误
- 在某些情况下(如链接层异常),断开重建可强制 Modem 重新协商参数
关键配置:
- 延迟:CarrierConfig[1] (默认 180000ms = 3分钟)
- 可跳过:CarrierConfig[1] (某些运营商的相同 APN 情况)
2.3 Level 2: RADIO_RESTART
目的:关闭再打开 Radio(不涉及 Modem 重启),强制重新注册网络
实现:
733| /** Recovery Action: RECOVERY_ACTION_RADIO_RESTART */
734| private void powerOffRadio() {
735| log("powerOffRadio: Restart radio");
736| mPhone.getServiceStateTracker().powerOffRadioSafely();
737| }
调用链:
DataStallRecoveryManager.powerOffRadio()
→ ServiceStateTracker.powerOffRadioSafely()
→ RIL_REQUEST_RADIO_POWER (power = false)
→ (等待 Modem 响应,关闭 RF 发射)
→ [延迟 ~3 秒]
→ RIL_REQUEST_RADIO_POWER (power = true)
→ (Modem 重新打开 RF,发起网络注册)
→ ServiceState 从 POWER_OFF → SEARCHING → IN_SERVICE
状态转换:
┌─ 1. Radio 关闭
│ └─ RIL_REQUEST_RADIO_POWER(false)
│ → Modem 关闭 RF 发射
│ → ServiceState → POWER_OFF
│
├─ 2. [系统等待物理重启]
│ └─ ~100-500ms delay
│
├─ 3. Radio 打开
│ └─ RIL_REQUEST_RADIO_POWER(true)
│ → Modem 重新打开 RF
│ → 重新发起网络注册过程
│
└─ 4. 网络重新注册
├─ CS domain attach (语音)
├─ PS domain attach (数据)
├─ 获取新的信号参数、LAC/TAC、小区信息
└─ 重建 PDP context(如果需要)
何时触发 Event_RADIO_STATE_CHANGED:
377| case EVENT_RADIO_STATE_CHANGED:
378| mRadioPowerState = mPhone.getRadioPowerState();
379| if (mDataStalled) {
380| // 只在数据卡死时记录 Radio 状态改变
381| mRadioStateChangedDuringDataStall = true;
382| ...
383| setRecoveryAction(mLastAction);
384| }
为什么有效:
- RF 关闭再打开强制 Modem 重新初始化 Radio 栈
- 清除可能的信号处理路径上的异常状态
- 强制重新获取网络参数和附着信息
关键配置:
- 延迟:CarrierConfig[2] (默认 180000ms = 3分钟)
- 可跳过:CarrierConfig[2]
2.4 Level 3: RESET_MODEM
目的:Modem 全系统重启,恢复 Modem 固件到初始状态
实现:
739| /** Recovery Action: RECOVERY_ACTION_RESET_MODEM */
740| private void rebootModem() {
741| log("rebootModem: reboot modem");
742| mPhone.rebootModem(null);
743| }
调用链:
DataStallRecoveryManager.rebootModem()
→ Phone.rebootModem(Message)
→ RIL.nvResetConfig() 或其他 Modem 重启命令
→ NV_RESET_CONFIG (可选)
→ MODEM_RESET (通过 SoC reset 或 watchdog)
Modem 重启过程:
┌─ 1. 发起 Modem 重启请求
│ └─ RIL_REQUEST_MODEM_RESET (AIDL)
│ 或 NV_RESET_CONFIG
│
├─ 2. Modem 断电或被 SoC 强制重置
│ ├─ Modem 固件终止所有进程
│ ├─ 所有状态变量清空
│ ├─ 无线小区连接断开
│ └─ PDN 上下文丢弃
│
├─ 3. Modem 重启(Boot & Initialization)
│ ├─ 固件加载和初始化
│ ├─ 硬件检测
│ ├─ NV Item 重载
│ └─ ~5-30 秒重启时间
│
└─ 4. Framework 重新初始化
├─ 监听 UNSOL_MODEM_RESTART indication
├─ ServiceState → POWER_OFF → SEARCHING → IN_SERVICE
├─ 重新建立所有数据连接
└─ DataNetwork 重新 SETUP_DATA_CALL
关键指示:
hardware/radio/1.0/IRadioIndication.hal:
oneway modemReset(RadioIndicationType type, string reason);
当 Modem 重启完成时,该 indication 会被上报到 Framework
为什么有效(最后的手段):
- Modem 固件级别的完全恢复
- 清除所有可能的 memory leak、state machine 异常
- 重新加载 NV Item(可能包含故障的配置)
约束条件:
- 最终恢复步骤:仅在其他方式都失败后执行
- 无限延迟:达到 RESET_MODEM 后,如果仍然失败,则标记
mIsAttemptedAllSteps = true,停止进一步恢复
关键配置:
- 延迟:CarrierConfig[3] (默认 180000ms = 3分钟,实际上不再延迟,因为是最后步骤)
- 可跳过:CarrierConfig[3]
三、恢复条件判断 & 约束条件
3.1 恢复是否需要的判定 (isRecoveryNeeded)
780| private boolean isRecoveryNeeded(boolean isNeedToCheckTimer) {
781| // Skip if network is invalid and recovery was not started yet
782| if (!mIsValidNetwork && !isRecoveryAlreadyStarted()) {
783| logl("skip when network still remains invalid...");
784| return false; // ← 网络仍然无效,恢复尚未开始
785| }
786|
787| // Skip recovery if we have already attempted all steps.
788| if (mIsAttemptedAllSteps) {
789| logl("skip retrying continue recovery action");
790| return false; // ← 已尝试所有步骤,放弃
791| }
792|
793| // To avoid back to back recovery, wait for a grace period
794| if (getElapsedTimeSinceRecoveryMs() < getDataStallRecoveryDelayMillis(mLastAction)
795| && isNeedToCheckTimer) {
796| logl("skip back to back data stall recovery");
797| return false; // ← 距离上次恢复时间不足,避免频繁触发
798| }
799|
800| // Skip recovery if it can cause a call to drop
801| if (mPhone.getState() != PhoneConstants.State.IDLE
802| && getRecoveryAction() > RECOVERY_ACTION_CLEANUP) {
803| logl("skip data stall recovery as there is an active call");
804| return false; // ← 通话中,避免 RADIO_RESTART/RESET_MODEM
805| }
806|
807| // Skip when poor signal strength
808| if (mPhone.getSignalStrength().getLevel() <= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
809| logl("skip data stall recovery as in poor signal condition");
810| return false; // ← 信号太差,恢复无意义
811| }
812|
813| if (!mDataNetworkController.isInternetDataAllowed(...)) {
814| logl("skip data stall recovery as data not allowed.");
815| return false; // ← 用户禁用了移动数据或网络策略不允许
816| }
817|
818| if (!mIsInternetNetworkConnected) {
819| logl("skip data stall recovery as data not connected");
820| return false; // ← 无互联网连接(可能是 IWLAN 等)
821| }
822| return true; // ← 所有条件满足,允许恢复
823| }
恢复触发的完整流程
ConnectivityService 网络验证失败
↓
onInternetValidationStatusChanged(NOT_VALID)
↓
setNetworkValidationState(false) // 标记 mDataStalled = true
↓
isRecoveryNeeded(true)?
├─ Yes → 发送 EVENT_SEND_DATA_STALL_BROADCAST
│ ↓
│ [等待 DSRM_PREDICT_WAITING_MILLIS (1000ms)]
│ ↓
│ doRecovery() 执行当前恢复动作
│ ↓
│ startNetworkCheckTimer(mLastAction)
│ ↓
│ [等待 getDataStallRecoveryDelayMillis(action) 时间]
│ ↓
│ 自动触发下一个 EVENT_SEND_DATA_STALL_BROADCAST
│
└─ No → 记录日志,等待下次网络验证
3.2 智能跳过恢复步骤
某些运营商配置允许跳过特定步骤:
620| public void setRecoveryAction(@RecoveryAction int action) {
621| ...
640| // 检查配置是否需要跳过该步骤
641| if (shouldSkipRecoveryAction(mRecoveryAction)) {
642| switch (mRecoveryAction) {
643| case RECOVERY_ACTION_GET_DATA_CALL_LIST:
644| setRecoveryAction(RECOVERY_ACTION_CLEANUP); // 直接跳到 CLEANUP
645| break;
646| case RECOVERY_ACTION_CLEANUP:
647| setRecoveryAction(RECOVERY_ACTION_RADIO_RESTART); // 直接跳到 RADIO_RESTART
648| break;
649| case RECOVERY_ACTION_RADIO_RESTART:
650| setRecoveryAction(RECOVERY_ACTION_RESET_MODEM); // 直接跳到 RESET_MODEM
651| break;
652| case RECOVERY_ACTION_RESET_MODEM:
653| resetAction(); // 放弃,复位
654| break;
655| }
656| }
...
657| }
何时跳过:
- 某些运营商为相同 APN(如同时用于互联网和 IMS 的 APN)
- CLEANUP 可能无效,因为 Modem 不会断开
- 此时配置会直接跳到 RADIO_RESTART
四、恢复流程中的计时器与状态管理
4.1 核心状态变量
165-166| @RecoveryAction
166| private int mRecoveryAction; // 当前恢复动作
183| @RecoveryAction
184| private int mLastAction; // 上次执行的恢复动作
173| private boolean mRecoveryTriggered; // 恢复是否已触发
175| private boolean mDataStalled; // 是否处于卡死状态
177| private boolean mLastActionReported; // 上次动作是否已上报
181| public long mDataStallStartMs; // 卡死开始时间
189| private boolean mNetworkCheckTimerStarted; // 计时器是否已启动
190| private boolean mRadioStateChangedDuringDataStall; // Radio 状态在卡死期间是否改变
191| private boolean mIsAirPlaneModeEnableDuringDataStall; // 卡死期间飞行模式是否启用
195| private boolean mMobileDataChangedToEnabledDuringDataStall; // 卡死期间移动数据是否从禁用变启用
197| private boolean mIsAttemptedAllSteps; // 是否已尝试所有步骤
4.2 计时器逻辑
750| private void startNetworkCheckTimer(@RecoveryAction int action) {
751| // 最后一步(RESET_MODEM)不再设置计时器
752| if (action == RECOVERY_ACTION_RESET_MODEM) return;
753|
754| if (!mNetworkCheckTimerStarted) {
755| mNetworkCheckTimerStarted = true;
756| mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
757| // 发送延迟消息,触发下一次恢复尝试
758| sendMessageDelayed(
759| obtainMessage(EVENT_SEND_DATA_STALL_BROADCAST),
760| getDataStallRecoveryDelayMillis(action)); // 默认 3 分钟
761| }
762| }
计时流程示例:
时刻 T=0
└─ 检测到数据卡死 (VALIDATION_STATUS_NOT_VALID)
└─ 触发 doRecovery()
└─ 执行 getDataCallList()
└─ 设置下一动作:mRecoveryAction = CLEANUP
└─ startNetworkCheckTimer(GET_DATA_CALL_LIST)
└─ sendMessageDelayed(..., 180000ms)
时刻 T=180s
└─ 网络验证再次失败
│ └─ 触发 doRecovery()
│ └─ 执行 cleanUpDataNetwork()
│ └─ 设置下一动作:mRecoveryAction = RADIO_RESTART
│ └─ startNetworkCheckTimer(CLEANUP)
│ └─ sendMessageDelayed(..., 180000ms)
│
└─ 或者网络验证成功
└─ 触发 reset()
└─ 恢复到初始状态
└─ mDataStalled = false
└─ mRecoveryAction = GET_DATA_CALL_LIST
4.3 恢复条件的动态调整
627-639| if (mMobileDataChangedToEnabledDuringDataStall
630| && mRecoveryAction < RECOVERY_ACTION_RADIO_RESTART) {
631| mRecoveryAction = RECOVERY_ACTION_RADIO_RESTART; // 直接跳到第二级
632| }
633|
634| if (mRadioStateChangedDuringDataStall
636| && mRadioPowerState == TelephonyManager.RADIO_POWER_ON) {
637| mRecoveryAction = RECOVERY_ACTION_RESET_MODEM; // 直接跳到第三级
638| }
含义:
- 用户启用移动数据(从禁用变启用):跳过 CLEANUP,直接尝试 RADIO_RESTART
- Radio 在卡死期间重启:说明系统已经执行了 RADIO_RESTART,直接尝试 RESET_MODEM
五、指标和诊断
5.1 数据卡死恢复统计
693-717| private void broadcastDataStallDetected(@RecoveryAction int recoveryAction) {
694| Intent intent = new Intent(TelephonyManager.ACTION_DATA_STALL_DETECTED);
697| intent.putExtra(TelephonyManager.EXTRA_RECOVERY_ACTION, recoveryAction);
699|
700| final boolean isRecovered = !mDataStalled;
701| final int duration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
702| @RecoveredReason final int reason = getRecoveredReason(mIsValidNetwork);
703| final int durationOfAction = (int) getDurationOfCurrentRecoveryMs();
704|
710| Bundle bundle = mStats.getDataStallRecoveryMetricsData(
711| recoveryAction, isRecovered, duration, reason,
712| mValidationCount, mActionValidationCount, durationOfAction);
713| intent.putExtra("EXTRA_DSRS_STATS_BUNDLE", bundle);
714|
717| mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE);
}
上报的关键指标:
isRecovered: 是否已恢复duration: 从卡死开始到恢复的总耗时reason: 恢复原因(DSRM、Modem、User、None)validationCount: 总验证次数actionValidationCount: 当前动作的验证次数durationOfAction: 当前恢复动作的执行耗时
5.2 ConnectivityDiagnostics 集成
数据卡死恢复统计会获取以下诊断数据:
185-191| public void onDataStallSuspected(@NonNull DataStallReport report) {
186| PersistableBundle bundle = report.getStallDetails();
187| mTcpMetricsCollectionPeriodMillis =
188| bundle.getInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS);
189| mTcpPacketFailRate = bundle.getInt(KEY_TCP_PACKET_FAIL_RATE);
190| mDnsConsecutiveTimeouts = bundle.getInt(KEY_DNS_CONSECUTIVE_TIMEOUTS);
191| }
诊断信息:
- DNS 连续超时数:连续 DNS 查询失败的次数
- TCP 包失败率:TCP 包送达失败的百分比
- TCP 指标收集周期:这些指标的统计周期
六、完整流程图
数据卡死场景
↓
┌─────────────────────────────────────────────────────────────┐
│ Step 1: 检测阶段 │
│ - NetworkMonitor 监测到 DNS 超时或 TCP 失败率高 │
│ - ConnectivityService 通知验证失败 │
│ - onInternetValidationStatusChanged(NOT_VALID) │
└─────────────────────┬───────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Step 2: 触发检查 │
│ - mDataStalled = true │
│ - isRecoveryNeeded() 判断是否满足恢复条件 │
│ ├─ 有活跃通话?→ 跳过高级别恢复 (RADIO_RESTART+) │
│ ├─ 用户禁用数据?→ 放弃恢复 │
│ ├─ 信号太差?→ 放弃恢复 │
│ └─ 已尝试所有步骤?→ 放弃恢复 │
└─────────────────────┬───────────────────────────────────────┘
↓
┌───────────────────────┐
│ 恢复条件满足? │
└───┬─────────┬─────────┘
│ YES │ NO
↓ └─→ 等待下次验证
↓
┌──────────────────────────────────────────────────────────────┐
│ Step 3: 恢复执行 (doRecovery) │
│ │
│ ┌─ Action 0: GET_DATA_CALL_LIST │
│ │ └─ requestDataCallList() 查询数据连接列表 │
│ │ └─ [等待 180s] │
│ │
│ ├─ Action 1: CLEANUP │
│ │ └─ onDataStallReestablishInternet() │
│ │ ├─ 断开所有 Internet PDN (DEACTIVATE_DATA_CALL) │
│ │ └─ 等待网络请求重建 (SETUP_DATA_CALL) │
│ │ └─ [等待 180s] │
│ │
│ ├─ Action 2: RADIO_RESTART │
│ │ └─ powerOffRadioSafely() │
│ │ ├─ RIL_REQUEST_RADIO_POWER(false) 关闭 RF │
│ │ ├─ [延迟 ~100-500ms] │
│ │ └─ RIL_REQUEST_RADIO_POWER(true) 打开 RF │
│ │ └─ [等待 180s 或更长,重新注册网络] │
│ │
│ └─ Action 3: RESET_MODEM │
│ └─ rebootModem() │
│ ├─ RIL_REQUEST_MODEM_RESET │
│ ├─ Modem 完全重启 (~5-30s) │
│ ├─ Framework 监听 UNSOL_MODEM_RESTART │
│ └─ 重建所有数据连接 │
│ └─ [不再等待,标记 mIsAttemptedAllSteps = true] │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ Step 4: 验证结果 │
│ │
│ ┌─ 验证通过 (VALIDATION_STATUS_VALID) │
│ │ └─ reset() 恢复到初始状态 │
│ │ ├─ mDataStalled = false │
│ │ ├─ mRecoveryAction = GET_DATA_CALL_LIST │
│ │ ├─ mIsAttemptedAllSteps = false │
│ │ └─ 发送 ACTION_DATA_STALL_DETECTED 广播 │
│ │
│ └─ 验证继续失败 │
│ └─ [延迟后] 触发下一级恢复 │
│ 或 [已尝试所有步骤] 放弃恢复 │
└──────────────────────────────────────────────────────────────┘
七、关键配置项
CarrierConfigManager.KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY
├─ [0]: GET_DATA_CALL_LIST → CLEANUP 间隔 (默认 180000ms)
├─ [1]: CLEANUP → RADIO_RESTART 间隔 (默认 180000ms)
├─ [2]: RADIO_RESTART → RESET_MODEM 间隔 (默认 180000ms)
└─ [3]: RESET_MODEM 后的间隔 (默认 180000ms,实际不用)
CarrierConfigManager.KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_ARRAY
├─ [0]: 是否跳过 GET_DATA_CALL_LIST
├─ [1]: 是否跳过 CLEANUP (某些相同 APN 情况)
├─ [2]: 是否跳过 RADIO_RESTART
└─ [3]: 是否跳过 RESET_MODEM
Settings.Global.DSRM_ENABLED_ACTIONS
└─ "true,true,false,true" 格式,允许动态禁用特定恢复动作
Settings.Global.DSRM_DURATION_MILLIS
└─ "180000,180000,180000,180000" 格式,允许动态调整延迟
八、总结
数据卡死恢复是一个精心设计的四级递进容错机制:
| 级别 | 名称 | 作用范围 | 风险等级 | 恢复时间 |
|---|---|---|---|---|
| 0 | GET_DATA_CALL_LIST | 状态查询、同步 | 无 | <1s |
| 1 | CLEANUP | 连接重建、Channel 清理 | 低 | <10s |
| 2 | RADIO_RESTART | RF 重启、重新注册 | 中 | ~30-60s |
| 3 | RESET_MODEM | 完全Modem重启 | 高 | ~5-30s + 重连 |
关键特性:
- ✅ 渐进式:从轻量级到重量级逐步升级
- ✅ 自适应:根据运营商配置跳过不必要的步骤
- ✅ 安全:通话中避免高风险恢复、信号差时放弃恢复
- ✅ 可配置:每个步骤的延迟和是否执行都可动态配置
- ✅ 可诊断:详细的上报恢复事件和诊断数据
现在让我更新 TODO 列表:
完美!现在让我提供几个关键代码片段的详细分析,以供你进一步深入理解:
📌 关键代码深度分析
1. 恢复循环的核心逻辑
@Override
public void handleMessage(Message msg) {
logv("handleMessage = " + msg);
switch (msg.what) {
case EVENT_SEND_DATA_STALL_BROADCAST:
mRecoveryTriggered = true;
// Verify that DSRM needs to process the recovery action
if (!isRecoveryNeeded(false)) {
cancelNetworkCheckTimer();
startNetworkCheckTimer(getRecoveryAction());
return;
}
// Broadcast intent that data stall has been detected.
broadcastDataStallDetected(getRecoveryAction());
// Schedule the message to wait for the predicted value.
sendMessageDelayed(
obtainMessage(EVENT_DO_RECOVERY), mPredictWaitingMillis);
break;
case EVENT_DO_RECOVERY:
doRecovery();
break;
case EVENT_RADIO_STATE_CHANGED:
mRadioPowerState = mPhone.getRadioPowerState();
if (mDataStalled) {
// Store the radio state changed flag only when data stall occurred.
mRadioStateChangedDuringDataStall = true;
if (Settings.Global.getInt(
mPhone.getContext().getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON,
0) != 0) {
mIsAirPlaneModeEnableDuringDataStall = true;
}
setRecoveryAction(mLastAction);
}
break;
case EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED:
updateGlobalConfigActions();
break;
case EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED:
updateGlobalConfigDurations();
break;
default:
loge("Unexpected message = " + msg);
break;
}
}
关键点分析:
-
EVENT_SEND_DATA_STALL_BROADCAST 是恢复循环的关键触发点,它会:
- 检查
isRecoveryNeeded(false)判断是否真的需要恢复(不再等待延迟) - 如果需要,发送 DSRS Broadcast(用于诊断)
- 延迟
mPredictWaitingMillis(通常 1000ms)后发送 EVENT_DO_RECOVERY
- 检查
-
EVENT_RADIO_STATE_CHANGED 会自动升级恢复步骤,如果系统已经自己重启了 Radio(可能由其他模块触发),则直接跳到 RESET_MODEM
2. 验证状态变化处理
private void onInternetValidationStatusChanged(@ValidationStatus int status) {
logl("onInternetValidationStatusChanged: " + DataUtils.validationStatusToString(status));
final boolean isValid = status == NetworkAgent.VALIDATION_STATUS_VALID;
if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
mValidationCount += 1;
mActionValidationCount += 1;
}
setNetworkValidationState(isValid);
if (isValid) {
if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
// Broadcast intent that data stall recovered.
broadcastDataStallDetected(mLastAction);
}
reset();
} else if (isRecoveryNeeded(true)) {
// Set the network as invalid, because recovery is needed
mIsValidNetwork = false;
log("trigger data stall recovery");
mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
sendMessage(obtainMessage(EVENT_SEND_DATA_STALL_BROADCAST));
}
}
验证流程:
- 验证通过 → 立即
reset()复位,恢复完成 - 验证失败 + 满足恢复条件 → 立即触发
EVENT_SEND_DATA_STALL_BROADCAST进入恢复循环 - 验证失败 + 不满足恢复条件 → 记录日志,等待下次验证
3. CLEANUP 的实际执行
private void onDataStallReestablishInternet() {
log("onDataStallReestablishInternet: Tear down data networks that support internet.");
// Tear down all data networks that support internet. After data disconnected, unsatisfied
// network requests will be re-evaluate again and data network controller will attempt to
// setup data networks to satisfy them.
mDataNetworkList.stream()
.filter(DataNetwork::isInternetSupported)
.forEach(dataNetwork -> dataNetwork.tearDown(
DataNetwork.TEAR_DOWN_REASON_DATA_STALL));
}
重要细节:
- 仅断开
isInternetSupported()的网络(通常是默认 APN) - 不会断开 IMS、MMS 等特殊用途的 PDN
- 断开后,
DataNetworkController会根据待决的网络请求自动重建 - 这是一个"轻量级"恢复,因为没有涉及 Radio 或 Modem
4. 动态调整恢复步骤
public void setRecoveryAction(@RecoveryAction int action) {
// Reset the validation count for action change
if (mFeatureFlags.dsrsDiagnosticsEnabled() && mRecoveryAction != action) {
mActionValidationCount = 0;
}
mRecoveryAction = action;
// Check if the mobile data enabled is TRUE, it means that the mobile data setting changed
// from DISABLED to ENABLED, we will set the next recovery action to
// RECOVERY_ACTION_RADIO_RESTART due to already did the RECOVERY_ACTION_CLEANUP.
if (mMobileDataChangedToEnabledDuringDataStall
&& mRecoveryAction < RECOVERY_ACTION_RADIO_RESTART) {
mRecoveryAction = RECOVERY_ACTION_RADIO_RESTART;
}
// Check if the radio state changed from off to on, it means that the modem already
// did the radio restart, we will set the next action to RECOVERY_ACTION_RESET_MODEM.
if (mRadioStateChangedDuringDataStall
&& mRadioPowerState == TelephonyManager.RADIO_POWER_ON) {
mRecoveryAction = RECOVERY_ACTION_RESET_MODEM;
}
// To check the flag from DataConfigManager if we need to skip the step.
if (shouldSkipRecoveryAction(mRecoveryAction)) {
switch (mRecoveryAction) {
case RECOVERY_ACTION_GET_DATA_CALL_LIST:
setRecoveryAction(RECOVERY_ACTION_CLEANUP);
break;
case RECOVERY_ACTION_CLEANUP:
setRecoveryAction(RECOVERY_ACTION_RADIO_RESTART);
break;
case RECOVERY_ACTION_RADIO_RESTART:
setRecoveryAction(RECOVERY_ACTION_RESET_MODEM);
break;
case RECOVERY_ACTION_RESET_MODEM:
resetAction();
break;
}
}
log("setRecoveryAction: " + recoveryActionToString(mRecoveryAction));
}
三层智能逻辑:
- 用户操作检测:如果用户在卡死期间启用数据 → 跳过已执行的步骤
- System Event 检测:如果 Radio 在卡死期间重启 → 说明系统已执行 RADIO_RESTART,直接尝试 RESET_MODEM
- 运营商配置:根据运营商配置跳过不必要的步骤(递归调用)
5. 运营商配置的动态更新
private void updateGlobalConfigActions() {
String enabledActions = Settings.Global.getString(
mPhone.getContext().getContentResolver(),
Settings.Global.DSRM_ENABLED_ACTIONS);
if (!TextUtils.isEmpty(enabledActions)) {
String[] splitEnabledActions = enabledActions.split(",");
boolean[] enabledActionsArray = new boolean[splitEnabledActions.length];
for (int i = 0; i < enabledActionsArray.length; i++) {
enabledActionsArray[i] = Boolean.parseBoolean(splitEnabledActions[i].trim());
}
int minLength = Math.min(enabledActionsArray.length,
mSkipRecoveryActionArray.length);
// Update the skip recovery action array.
for (int i = 0; i < minLength; i++) {
mSkipRecoveryActionArray[i] = !enabledActionsArray[i];
}
log("SkipRecoveryAction: "
+ Arrays.toString(mSkipRecoveryActionArray));
mPredictWaitingMillis = DSRM_PREDICT_WAITING_MILLIS;
} else {
mPredictWaitingMillis = 0;
log("Enabled actions is null");
}
}
动态配置特性:
- 支持运营商通过
Settings.Global.DSRM_ENABLED_ACTIONS动态控制恢复步骤 - 格式:
"true,true,false,true"表示启用步骤 0、1、3,跳过步骤 2 - 也支持动态调整延迟时间:
Settings.Global.DSRM_DURATION_MILLIS
🎯 实战应用指南
问题排查
-
数据卡死频繁发生
- 检查 NetworkMonitor 的 DNS/TCP 检测阈值
- 检查网络信号强度和网络质量
- 查看 DSRM 是否真的执行了恢复步骤(查看日志)
-
恢复步骤没有执行
- 检查
shouldSkipRecoveryAction()是否因运营商配置跳过了 - 检查
isRecoveryNeeded()的各个条件是否满足 - 查看用户是否处于通话中、飞行模式、或禁用数据
- 检查
-
某个恢复步骤无效
- 对于 CLEANUP 无效 → 可能需要运营商配置跳过,直接尝试 RADIO_RESTART
- 对于 RADIO_RESTART 无效 → 可能是 Modem 固件问题,需要 RESET_MODEM
- 对于 RESET_MODEM 无效 → 可能是硬件或信号问题,超出 DSRM 范围
性能优化
-
减少卡死误报
- 增加 NetworkMonitor 的检测阈值
- 减少检测频率
-
加快恢复速度
- 减少 DSRM 延迟配置(CarrierConfig)
- 对于某些运营商,可以跳过不必要的步骤
-
降低用户影响
- 在通话中不执行 RADIO_RESTART/RESET_MODEM
- 信号差时不执行恢复(避免无谓的操作)
📚 相关文件索引
核心实现
├─ DataStallRecoveryManager.java (DSRM 主逻辑)
├─ DataNetworkController.java (数据网络管理)
├─ DataNetwork.java (单个 PDN 的连接管理)
└─ RIL.java (与 Modem 通信)
诊断和统计
├─ DataStallRecoveryStats.java (恢复统计)
├─ ConnectivityService.java (网络验证)
└─ NetworkMonitor (网络质量监测)
配置
├─ CarrierConfigManager.java (运营商配置)
├─ DataConfigManager.java (数据配置管理)
└─ Settings.Global (动态全局设置)
这就是 Android Telephony 中完整的数据恢复与容错机制!如有任何疑问,欢迎继续提问 🚀