Android Telephony 数据恢复与容错机制详解

7 阅读17分钟

一、数据卡死检测机制

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 = trueisRecoveryNeeded(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" 格式,允许动态调整延迟

八、总结

数据卡死恢复是一个精心设计的四级递进容错机制

级别名称作用范围风险等级恢复时间
0GET_DATA_CALL_LIST状态查询、同步<1s
1CLEANUP连接重建、Channel 清理<10s
2RADIO_RESTARTRF 重启、重新注册~30-60s
3RESET_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 是恢复循环的关键触发点,它会:

    1. 检查 isRecoveryNeeded(false) 判断是否真的需要恢复(不再等待延迟)
    2. 如果需要,发送 DSRS Broadcast(用于诊断)
    3. 延迟 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));
        }
    }

验证流程

  1. 验证通过 → 立即 reset() 复位,恢复完成
  2. 验证失败 + 满足恢复条件 → 立即触发 EVENT_SEND_DATA_STALL_BROADCAST 进入恢复循环
  3. 验证失败 + 不满足恢复条件 → 记录日志,等待下次验证

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));
    }

三层智能逻辑

  1. 用户操作检测:如果用户在卡死期间启用数据 → 跳过已执行的步骤
  2. System Event 检测:如果 Radio 在卡死期间重启 → 说明系统已执行 RADIO_RESTART,直接尝试 RESET_MODEM
  3. 运营商配置:根据运营商配置跳过不必要的步骤(递归调用)

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

🎯 实战应用指南

问题排查

  1. 数据卡死频繁发生

    • 检查 NetworkMonitor 的 DNS/TCP 检测阈值
    • 检查网络信号强度和网络质量
    • 查看 DSRM 是否真的执行了恢复步骤(查看日志)
  2. 恢复步骤没有执行

    • 检查 shouldSkipRecoveryAction() 是否因运营商配置跳过了
    • 检查 isRecoveryNeeded() 的各个条件是否满足
    • 查看用户是否处于通话中、飞行模式、或禁用数据
  3. 某个恢复步骤无效

    • 对于 CLEANUP 无效 → 可能需要运营商配置跳过,直接尝试 RADIO_RESTART
    • 对于 RADIO_RESTART 无效 → 可能是 Modem 固件问题,需要 RESET_MODEM
    • 对于 RESET_MODEM 无效 → 可能是硬件或信号问题,超出 DSRM 范围

性能优化

  1. 减少卡死误报

    • 增加 NetworkMonitor 的检测阈值
    • 减少检测频率
  2. 加快恢复速度

    • 减少 DSRM 延迟配置(CarrierConfig)
    • 对于某些运营商,可以跳过不必要的步骤
  3. 降低用户影响

    • 在通话中不执行 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 中完整的数据恢复与容错机制!如有任何疑问,欢迎继续提问 🚀