第3章 状态定义与枚举:崩溃采集、等待上报、上报中、上报成功、上报失败、重试中、丢弃
崩溃上报链路的核心是状态机——一组有限的状态以及状态之间的转移规则。整个链路从崩溃发生到最终处理结果,共包含7个关键状态。本章逐一定义每个状态的语义、触发条件、典型行为,并以枚举形式固化为代码。
状态机设计原则:每个状态应具有唯一性、可观测性和可测试性。状态转移必须由明确的事件驱动,避免隐式跳转和循环依赖。
3.1 状态定义表
| 状态名称 | 枚举值(推荐) | 语义 | 进入条件 | 典型行为 |
|---|---|---|---|---|
| 崩溃采集 | COLLECTING |
正在收集崩溃现场信息(堆栈、线程、日志、内存快照等) | 原生崩溃信号 or Java异常被捕获 | 写入文件、序列化上下文 |
| 等待上报 | WAITING |
采集完成,等待网络条件/用户授权/定时器触发 | 崩溃采集完成 | 检查网络、弹窗确认(可选) |
| 上报中 | UPLOADING |
正在向服务器发起HTTP/HTTPS请求 | 满足上报条件(网络可用、用户同意) | 上传文件、监听进度 |
| 上报成功 | SUCCESS |
服务器返回200且校验通过 | 上报完成且无错误 | 清理本地缓存、标记已上报 |
| 上报失败 | FAILED |
服务器返回错误、超时或网络中断 | 请求失败或异常 | 记录错误原因、触发重试策略 |
| 重试中 | RETRYING |
正在等待指数退避重试间隔 | 上报失败且重试次数未耗尽 | 定时器、更新重试计数 |
| 丢弃 | DISCARDED |
放弃该条崩溃数据(不再处理) | 重试次数耗尽/用户拒绝/采集超时/被动清理 | 删除文件、释放资源 |
3.2 状态枚举代码示例
在Android项目中,建议使用enum class定义状态,每个枚举值携带整数id用于日志和数据库存储:
public enum CrashReportState {
/** 崩溃采集 */
COLLECTING(1),
/** 等待上报 */
WAITING(2),
/** 上报中 */
UPLOADING(3),
/** 上报成功 */
SUCCESS(4),
/** 上报失败 */
FAILED(5),
/** 重试中 */
RETRYING(6),
/** 丢弃 */
DISCARDED(7);
private final int id;
CrashReportState(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
3.3 状态转移图
下图展示了7个状态之间的标准转移路径。实线箭头表示正常流程,虚线箭头表示异常或超时路径。其中“重试中”状态会通过指数退避重新回到“上报中”,形成循环,直至达到最大重试次数后进入“丢弃”。
3.4 状态转移清单
- 崩溃采集 → 等待上报:采集结束(成功或部分成功)后立即转移。
- 等待上报 → 上报中:网络可用且满足上报策略(如WIFI限制、用户已授权)。
- 等待上报 → 丢弃:等待超时(如30分钟)或用户通过界面选择“忽略”。
- 上报中 → 上报成功:服务器返回2xx且body校验通过。
- 上报中 → 上报失败:任何网络异常、服务器错误、超时或数据损坏。
- 上报失败 → 重试中:已重试次数 < 最大重试次数(通常3次)。
- 重试中 → 上报中:等待间隔(1s、2s、4s…)后触发。
- 重试中 → 丢弃:已达最大重试次数或总生存时间超限。
3.5 重试策略与丢弃阈值
重试中状态使用指数退避 + 随机抖动以减轻服务器压力。常见配置:
initialDelay = 1000ms // 首次重试前等待1秒
maxDelay = 30000ms // 最大等待30秒
multiplier = 2.0 // 每次翻倍
jitter = 0.2 // 随机扰动 ±20%
maxRetryCount = 3 // 超过后转入丢弃
注意:丢弃状态是最终态,一旦进入不可再触发任何重试或上报。务必确保丢弃前所有相关资源(文件句柄、内存缓存)已正确释放,避免内存泄漏。
3.6 状态与线程安全
状态枚举本身是线程安全的(枚举实例不可变),但状态转换操作(如从 WAITING 转到 UPLOADING)必须使用原子操作或同步锁,防止并发上报导致重复请求。推荐使用 AtomicReference<CrashReportState> 或 StateMachine 框架。
import java.util.concurrent.atomic.AtomicReference;
public class CrashReportStateMachine {
private final AtomicReference<CrashReportState> currentState =
new AtomicReference<>(CrashReportState.COLLECTING);
public boolean transitionTo(CrashReportState target) {
// 这里仅演示原子更新,实际需校验合法性
return currentState.compareAndSet(
currentState.get(), target);
}
}