Data recovery

3 阅读5分钟

1. 数据卡顿恢复管理器 (DataStallRecoveryManager)

这是电话数据连接中最重要的恢复机制:

恢复策略(4 个递进步骤)

初始状态:数据连接卡顿(无法访问网络)
                
步骤 1: RECOVERY_ACTION_GET_DATA_CALL_LIST
        ├─ 查询 RIL 获取当前数据连接状态
        ├─ 检查 IP 地址、DNS 等链路信息
        └─ 目的:检测是否只是状态不同步
                
步骤 2: RECOVERY_ACTION_CLEANUP
        ├─ 断开数据连接 (RIL_REQUEST_DEACTIVATE_DATA_CALL)
        ├─ 立即重新建立连接 (RIL_REQUEST_SETUP_DATA_CALL)
        └─ 目的:重新建立 RIL  Modem 之间的通道
                
步骤 3: RECOVERY_ACTION_RADIO_RESTART
        ├─ 关闭无线电 (RIL_REQUEST_RADIO_POWER)
        ├─ 重新开启无线电
        └─ 目的:让 Modem 重新附加到网络
                
步骤 4: RECOVERY_ACTION_RESET_MODEM
        ├─ 重启 Modem (NV_RESET_CONFIG)
        └─ 目的:解决 Modem 端的问题

关键代码(doRecovery 方法)

private void doRecovery() {
    @RecoveryAction final int recoveryAction = getRecoveryAction();
    final int signalStrength = mPhone.getSignalStrength().getLevel();

    // 记录事件
    TelephonyMetrics.getInstance()
            .writeDataStallEvent(mPhone.getPhoneId(), recoveryAction);
    mLastAction = recoveryAction;

    switch (recoveryAction) {
        case RECOVERY_ACTION_GET_DATA_CALL_LIST:
            logl("doRecovery(): get data call list");
            getDataCallList();  // 步骤 1
            setRecoveryAction(RECOVERY_ACTION_CLEANUP);
            break;
            
        case RECOVERY_ACTION_CLEANUP:
            logl("doRecovery(): cleanup all connections");
            cleanUpDataNetwork();  // 步骤 2
            setRecoveryAction(RECOVERY_ACTION_RADIO_RESTART);
            break;
            
        case RECOVERY_ACTION_RADIO_RESTART:
            logl("doRecovery(): restarting radio");
            setRecoveryAction(RECOVERY_ACTION_RESET_MODEM);
            powerOffRadio();  // 步骤 3
            break;
            
        case RECOVERY_ACTION_RESET_MODEM:
            logl("doRecovery(): modem reset");
            rebootModem();  // 步骤 4
            resetAction();
            mIsAttemptedAllSteps = true;
            break;
    }
    
    startNetworkCheckTimer(mLastAction);
}

恢复原因追踪

// 系统追踪数据恢复的原因
public @interface RecoveredReason {}

private static final int RECOVERED_REASON_NONE = 0;      // 未恢复
private static final int RECOVERED_REASON_DSRM = 1;      // DSRM 恢复
private static final int RECOVERED_REASON_MODEM = 2;     // Modem 自己恢复
private static final int RECOVERED_REASON_USER = 3;      // 用户手动恢复

2. 数据库损坏恢复 (DefaultDatabaseErrorHandler)

处理 SQLite 数据库腐蚀的机制:

工作流程

数据库腐蚀被检测到
        ↓
onCorruption() 被调用
        ↓
     判断数据库是否已打开?
        ├─ NO (未打开)
        │   └─ 直接删除数据库文件
        │       (文件无法打开说明已严重损坏)
        │
        └─ YES (已打开)
            ├─ 获取附加数据库列表
            ├─ 关闭数据库
            └─ 删除主数据库和所有附加数据库
                (防止残留的腐蚀文件影响后续操作)

关键代码

public void onCorruption(SQLiteDatabase dbObj) {
    Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
    SQLiteDatabase.wipeDetected(dbObj.getPath(), "corruption");

    // 检查数据库是否打开
    if (!dbObj.isOpen()) {
        // 数据库文件无法打开,直接删除
        deleteDatabaseFile(dbObj.getPath());
        return;
    }

    List<Pair<String, String>> attachedDbs = null;
    try {
        // 获取所有附加数据库
        try {
            attachedDbs = dbObj.getAttachedDbs();
        } catch (SQLiteException e) {
            /* 数据库太损坏,甚至无法执行此命令 */
        }
        
        // 关闭数据库
        try {
            dbObj.close();
        } catch (SQLiteException e) {
            /* 忽略错误继续 */
        }
    } finally {
        // 删除所有文件
        if (attachedDbs != null) {
            // 删除所有附加数据库
            for (Pair<String, String> p : attachedDbs) {
                deleteDatabaseFile(p.second);
            }
        } else {
            // 删除主数据库
            deleteDatabaseFile(dbObj.getPath());
        }
    }
}

private void deleteDatabaseFile(String fileName) {
    if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
        return;  // 不删除内存数据库
    }
    
    Log.e(TAG, "deleting the database file: " + fileName);
    try {
        SQLiteDatabase.deleteDatabase(new File(fileName), false);
    } catch (Exception e) {
        Log.w(TAG, "delete failed: " + e.getMessage());
    }
}

3. 应用数据备份和恢复

系统中有多层备份恢复机制:

完整备份恢复 (FullRestoreEngine)

备份数据存在
        ↓
应用安装
        ↓
FullRestoreEngine.onRestore()
        ├─ 读取备份数据流
        ├─ 启动应用 Agent
        ├─ 逐块传递数据给应用
        └─ 应用重建其数据状态

关键特性:

  • 应用负责清除旧数据
  • 如果恢复失败,OS 会自动清除应用数据
  • 支持 OBB (Opaque Binary Blob) 数据恢复

关键 API

public abstract void onRestore(
    BackupDataInput data,           // 备份数据输入流
    int appVersionCode,              // 备份时的应用版本
    ParcelFileDescriptor newState    // 新的恢复状态文件
) throws IOException;

4. 网络数据恢复 (NetworkStatsRecorder)

处理网络统计信息的恢复:

故障恢复策略

FileRotator 失败
        ↓
recoverAndDeleteData()
        ├─ 将状态转储到 DropBoxManager(用于调试)
        └─ 如果设置了 mWipeOnError
            └─ 删除所有数据文件并重新开始
void recoverAndDeleteData() {
    if (DUMP_BEFORE_DELETE) {
        final ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            mRotator.dumpAll(os);  // 转储状态用于调试
        } catch (IOException e) {
            os.reset();
        } finally {
            IoUtils.closeQuietly(os);
        }
        mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
    }
    
    // 如果设置了自动清除错误数据
    if (mWipeOnError) {
        mRotator.deleteAll();  // 删除所有文件
    }
}

5. 系统恢复 (RescueParty)

系统级别的故障恢复机制:

恢复级别 (Rescue Levels)

级别 1: 重置受信设置
        └─ 重置系统默认配置

级别 2: 清除应用缓存
        └─ 删除应用缓存数据

级别 3: 清除应用数据
        └─ 完全清除应用用户数据

级别 4: 恢复出厂设置
        └─ 清空用户数据,恢复系统默认状态

工作流程

应用多次崩溃或系统故障
        ↓
RescueParty 监测到
        ↓
尝试当前恢复级别
        ├─ 成功?
        │   └─ 记录事件,恢复完成
        │
        └─ 失败?
            └─ 升级到更激进的恢复级别
                    ↓
                最终重启到恢复模式

6. 数据恢复的分层策略

层级恢复对象恢复方式何时触发
1. 轻量级数据连接重新查询、清除、重启无线电数据卡顿
2. 中等级数据库检测腐蚀,删除文件,让应用重建数据库打开失败
3. 应用级应用数据从备份恢复数据应用安装、升级
4. 网络级网络统计删除损坏的统计数据文件操作失败
5. 系统级整个系统清除缓存、应用数据、恢复出厂系统故障、多次崩溃

7. 关键设计原则

1. 渐进式恢复 (Progressive Recovery)
   ├─ 先尝试轻量级操作
   ├─ 逐步升级到更激进的措施
   └─ 避免不必要的数据丢失

2. 错误隔离 (Error Isolation)
   ├─ 单个组件故障不影响整体系统
   ├─ 数据库腐蚀只删除该数据库
   └─ 应用崩溃不清除其他应用数据

3. 用户控制 (User Control)
   ├─ 用户可以手动恢复(如关闭/打开数据)
   ├─ 自动恢复前通常会通知用户
   └─ 系统记录所有恢复事件用于日志

4. 数据保留 (Data Retention)
   ├─ 删除前先转储数据用于调试
   ├─ 保存在 DropBox 供后续分析
   └─ 最后才是真正删除

5. 可监控性 (Observability)
   ├─ 记录所有恢复事件
   ├─ 追踪恢复的原因和时间
   └─ 使用 LocalLog 进行详细日志

8. 实际应用场景

场景触发恢复恢复机制
用户无法上网✓ 数据卡顿DSRM → 步骤 1-4
系统启动缓慢✓ 应用缓存损坏清除缓存
应用频繁崩溃✓ 系统故障RescueParty 恢复级别
数据库文件损坏✓ 数据库操作失败删除损坏文件
网络统计异常✓ 文件操作错误删除统计数据重新计算
OTA 更新失败✓ 更新过程错误回滚到前一版本或恢复

这个多层次、多策略的数据恢复机制确保了 Android 系统的鲁棒性和用户体验。