第4章 事件定义与触发:崩溃发生时、网络变化、定时器、手动触发、优先级处理
状态机由事件驱动。事件是状态转换的唯一原因,没有事件就没有流转。本章将明确定义4种核心事件,设计可扩展的事件数据模型,建立优先级仲裁机制,并完成事件源到状态机的完整链路接入。
4.1 事件体系总览
崩溃上报状态机需要处理不同来源、不同紧急程度的外部刺激。下表给出了4种事件类型的核心特征对比:
| 事件类型 | 来源 | 触发方式 | 默认优先级 | 说明 |
|---|---|---|---|---|
| 崩溃发生 (CrashEvent) | Native/Java Crash | 信号处理器 / 异常捕获 | 最高 | 核心事件,触发整个上报链路启动 |
| 网络变化 (NetworkEvent) | 系统广播 / NetworkCallback | 被动接收 | 高 | 影响上报策略、重试时机和日志聚合 |
| 定时器 (TimerEvent) | AlarmManager / Handler | 时间到达 | 中 | 用于延迟聚合、强制上报、保活心跳 |
| 手动触发 (ManualEvent) | 外部开发API调用 | 外界请求 | 低 | 用户反馈反馈、开发者日志同步、设置变更 |
4.2 事件定义与数据结构
为了类型安全和可扩展性,我们使用Kotlin密封类(Sealed Class)定义所有事件类型。每个事件携带自身特有的元数据,并显式声明优先级权重:
核心代码:事件密封类定义
sealed class UploadEvent(val priority: Int) {
data class CrashEvent(
val timestamp: Long,
val threadName: String,
val stackTrace: String,
val priority: Int = Int.MAX_VALUE
) : UploadEvent(priority)
data class NetworkEvent(
val isAvailable: Boolean,
val type: Int, // WIFI, MOBILE, NONE
val priority: Int = 100
) : UploadEvent(priority)
data class TimerEvent(
val taskId: String,
val triggerTime: Long,
val priority: Int = 50
) : UploadEvent(priority)
data class ManualEvent(
val reason: String,
val priority: Int = 10
) : UploadEvent(priority)
}
设计要点: Int.MAX_VALUE 确保崩溃事件在优先级队列中永远排在队首。网络事件使用 100 作为高优先级基数,以便在未来扩展子优先级(如WIFI为101,移动网络为99)。
4.3 事件触发原理解析
4.3.1 崩溃发生时(CrashEvent)
Thread.setDefaultUncaughtExceptionHandler 捕获Java层未处理异常;JNI层通过 sigaction() 注册 SIGABRT、SIGSEGV 等信号处理器捕获Native崩溃。在信号处理器上下文中,只做最少工作——将原始 siginfo_t 和 ucontext 写入共享内存(shmem),立即返回。事件循环从shmem中读取数据,组装 CrashEvent 并注入状态机。
信号处理器安全规则: 信号处理器内部禁止调用 malloc、free、pthread_mutex_lock 等不可重入函数。一旦在信号处理器中再次崩溃,必须调用 _exit() 终止进程,防止无限循环