AppErrorsTracking 数据持久化剖析:JSON 存储机制与旧版数据自动迁移原理
【免费下载链接】AppErrorsTrackingAdded more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.项目地址: https://gitcode.com/gh_mirrors/ap/AppErrorsTracking
AppErrorsTracking 是一款增强 Android 崩溃对话框的开源追踪工具,核心能力是把应用崩溃记录持久化到本地。本文剖析它的JSON 存储机制与旧版数据自动迁移原理,带你用 5 分钟看懂崩溃数据是如何被写入、读取并平滑升级的。
一、AppErrorsTracking 是什么?
AppErrorsTracking 运行在 Android 系统服务进程中,目标是替换原生简陋的崩溃对话框,并在应用崩溃后做到三件事:
- 🪟 弹出更美观、功能更完整的自定义崩溃对话框
- 💾 将每一次崩溃(包名、异常类、完整堆栈、版本信息等)持久化保存
- 🔍 在应用内查看、筛选、分享、导出崩溃历史
模块的注入入口在 HookEntry.kt,启动后加载系统框架钩子 FrameworkHooker.kt,而全部持久化逻辑集中在一个文件:AppErrorsRecordData.kt。
二、崩溃记录的产生:从系统钩子到数据对象
应用崩溃时,系统会走到ActivityManagerService的handleAppCrashInActivityController方法。AppErrorsTracking 通过钩子接管这个时机(FrameworkHooker.kt#L493-L504),从系统错误报告中提取崩溃信息,封装为 AppErrorsInfoBean 数据对象。
该对象共 17 个字段,一次崩溃的"体检报告"如下:
| 字段 | 说明 |
|---|---|
pid/userId | 进程 ID 与用户 ID(支持多用户环境) |
packageName | 崩溃应用包名 |
versionName/versionCode | 应用版本名称与版本号 |
cpuAbi/targetSdk/minSdk | CPU 架构与 SDK 信息 |
isNativeCrash | 是否为 Native 层崩溃 |
exceptionClassName/exceptionMessage | 异常类名与异常信息 |
throwClassName/throwFileName/throwMethodName/throwLineNumber | 异常抛出的精确位置 |
stackTrace | 完整异常堆栈 |
timestamp | 崩溃时间戳 |
Native 崩溃(如 SIGSEGV)会被特别识别:判断异常类名是否为native crash,并尝试从堆栈中提取Abort message作为异常信息,让 C++ 崩溃同样有可读的摘要(AppErrorsInfoBean.kt#L110-L136)。
三、JSON 存储机制:一次崩溃 = 一个 JSON 文件
这是 AppErrorsTracking 数据持久化最核心的设计:每一条崩溃记录都以独立的 JSON 文件落盘,而不是挤在一张表或一个大文件里。
3.1 存储位置:系统分区目录
数据目录固定为(AppErrorsRecordData.kt#L44):
/data/misc/app_errors_records/它位于系统分区而非应用私有目录,带来两个好处:
- 崩溃记录不随普通应用卸载而丢失
- 对运行在系统服务中的模块天然可读写
目录在模块启动时由initializeDataDirectory()自动创建,创建失败会打印错误日志但不中断流程(AppErrorsRecordData.kt#L75-L81)。
3.2 文件命名规则:唯一且自描述
每条记录的文件名由三段信息拼接(AppErrorsInfoBean.kt#L149):
{包名}_{进程ID}_{时间戳}.json 例如:com.example.app_12345_1716000000000.json这套命名带来三个好处:
- 文件名全局唯一,不同进程、不同时刻的记录不会互相覆盖
- 按文件修改时间倒序即可得到崩溃时间线(AppErrorsRecordData.kt#L59)
- 单条记录损坏不影响其他记录,隔离了故障范围
3.3 序列化:Gson 宽松模式 + 失败安全
JSON 序列化由 GsonFormatFactory.kt 提供:
- 全局懒加载的
Gson实例,开启setLenient()宽松模式 - 提供
toJsonOrNull/toEntityOrNull等"失败返回 null"的安全包装
也就是说,任何一次序列化或解析异常都只会"跳过这一条",绝不会让整个崩溃记录功能崩溃——这对运行在系统进程中的模块至关重要。
3.4 内存 + 磁盘双写
AppErrorsRecordData采用"内存列表 + 磁盘文件"双写架构:
| 层 | 数据结构 | 作用 |
|---|---|---|
| 内存 | CopyOnWriteArrayList<AppErrorsInfoBean> | 多线程读写安全,UI 查询快 |
| 磁盘 | 独立 JSON 文件 | 断电不丢、跨重启保留 |
新增记录的流程(AppErrorsRecordData.kt#L118-L121):
- 新记录插入内存列表头部(最新崩溃置顶)
- 序列化为 JSON 文本
- 以固定文件名写入数据目录
启动时由onCreate生命周期钩子触发init()(FrameworkHooker.kt#L227):确保目录存在 → 读取目录下全部 JSON 文件 → 反序列化装填内存列表,读取顺序按修改时间倒序,天然对应 UI 上"最近崩溃在前"的展示顺序。
3.5 为什么选"每崩溃一个文件"而不是数据库?
| 方案 | 主要缺点 |
|---|---|
| SharedPreferences | 全部记录挤在一个 XML,每次新增都要重写整个文件 |
| SQLite | 需建表、处理 schema 迁移与并发锁 |
| 单个大 JSON 文件 | 同上,且一处损坏全部丢失 |
| 每条记录独立 JSON 文件✅ | 原子写入、损坏隔离、按文件名即可检索 |
崩溃记录是典型的"只增很少改"场景,独立文件方案与它最为匹配。
四、旧版数据自动迁移原理
早期版本把所有崩溃记录序列化成一个大 JSON 字符串,整体存放在系统 Settings 数据库的 Secure 表(键名app_errors_data)。这种"大杂烩"式存储读取慢、无法隔离损坏,新版改为独立 JSON 文件方案,并内置了一次性的自动迁移逻辑。
4.1 迁移流程:读旧 → 转新 → 清旧
每次启动加载数据时,readAllDataFromFiles()会优先尝试迁移函数copyOldDataFromResolverString()(AppErrorsRecordData.kt#L87-L112):
- 读旧:从
Settings.Secure读取app_errors_data键值 - 转新:用 Gson 将大 JSON 解析为记录列表,逐条补全新版字段(
cpuAbi、versionName、versionCode都是后续版本才加入的字段),并写入各自的 JSON 文件 - 清旧:将旧键值置为空字符串,完成交接
4.2 迁移的健壮性设计
- 幂等:旧键被清空后,下次读取得到空串、解析返回 null,自动走文件读取分支——迁移只发生一次,绝不重复
- 容错:整个迁移包裹在
runCatching中,即使旧数据格式异常也不会阻塞新版文件加载 - 兼容:新版缺失字段用
unknown/-1兜底,老记录迁移后依然可完整展示
这套"读旧 → 转新 → 清旧"模式,是 Android 工具模块升级存储方案时值得借鉴的经典做法。✨
五、数据生命周期管理
AppErrorsRecordData提供完整的增删清能力,并通过事件回调与 UI 联动(FrameworkHooker.kt#L248-L255):
| 操作 | 方法 | 行为 |
|---|---|---|
| 新增 | add(bean) | 内存置顶 + 写 JSON 文件 |
| 删除单条 | remove(bean) | 内存移除 + 删除对应文件 |
| 清空全部 | clearAll() | 清空列表 + 递归删除目录 + 重建目录 |
所有文件操作都被runCatching保护:即使文件系统出现异常,也只会在内存与磁盘之间短暂不一致,而不会抛出未捕获异常拖垮宿主进程。
六、配置数据与记录数据的分离存储
AppErrorsTracking 把两类数据彻底分开,这是架构上另一个值得学习的设计:
| 数据类型 | 存储方式 | 位置 |
|---|---|---|
| 崩溃记录 | 独立 JSON 文件 | /data/misc/app_errors_records/ |
| 应用配置 | SharedPreferences(PrefsData封装) | 模块私有偏好文件 |
配置项(如"仅前台显示对话框"、"Material 3 风格")由 ConfigData.kt 统一管理;而 AppErrorsConfigData.kt 还维护了按应用区分的"对话框 / 通知 / Toast / 不显示"四套展示模板。
两类存储互不干扰:崩溃记录再多也不会撑爆配置文件,清空记录也不会误删用户配置。🧩
七、设计亮点速览
| 亮点 | 收益 |
|---|---|
| 一次崩溃一个文件 | 写入原子、损坏隔离、天然增量 |
| 内存列表 + 磁盘文件双写 | 查询快、断电不丢 |
| 读旧 → 转新 → 清旧迁移 | 幂等、容错、用户无感升级 |
| 记录与配置分离存储 | 各取所需,互不影响 |
全链路runCatching容错 | 存储异常不影响主功能 |
八、相关源码索引
- 数据持久化核心(存储 + 迁移):AppErrorsRecordData.kt
- 崩溃数据对象(17 字段 + 文件名规则):AppErrorsInfoBean.kt
- JSON 序列化工具:GsonFormatFactory.kt
- 系统钩子与事件分发:FrameworkHooker.kt
- 模块注入入口:HookEntry.kt
- 全局配置存储:ConfigData.kt
- 应用展示模板配置:AppErrorsConfigData.kt
【免费下载链接】AppErrorsTrackingAdded more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.项目地址: https://gitcode.com/gh_mirrors/ap/AppErrorsTracking
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考