Firebase Crashlytics 如何自动收集崩溃堆栈和设备信息?
解读
国内面试中,这道题常被用来验证候选人是否“真上线过”。很多简历写“接入 Crashlytics”,但追问下去只能答出“gradle 加依赖、控制台看堆栈”。面试官想听到的是:崩溃信号如何被捕获、数据怎样在进程濒死时落地、如何确保下次启动上传成功、以及国内 ROM 后台限制对上传的影响。答到这一层,才能证明你不仅“用过”,而且“踩过坑”。
知识点
- 信号监听层:JVM 层通过 Thread.setDefaultUncaughtExceptionHandler 接管未捕获异常;Native 层通过 sigaction 注册 SIGSEGV、SIGABRT 等信号。
- 濒死写盘:崩溃线程在 handler 内部通过 mmap 打开一个“cls”文件(Google 自研的“CrashlyticsCore”格式),写入时间戳、进程信息、线程回溯、寄存器、maps、日志缓冲区,然后立即 fsync,确保 8 KB 以内完成,降低被系统杀死的概率。
- 后台上传策略:进程重启后由 ContentProvider 触发初始化,WorkManager 调度 OneTimeWorkRequest,网络可用时上传;国内无 GMS 设备走 Google Play 服务 fallback,实际使用 Firebase Installations SDK 的 HTTPS 直连 firebasecrashlytics.googleapis.com,需配置国内代理或允许列表。
- 设备信息来源:Build.* 字段、Settings.Secure.ANDROID_ID、Linux 版本、ABI 列表、内存等级、电池电量、Root 状态(通过 su 探测)、Google Play 服务版本、国内渠道号(通过 AndroidManifest 的 meta-data 注入)。
- 混淆还原:上传 APK/AAB 时同步上传 R8 生成的 mapping.txt,Crashlytics Gradle Plugin 在 package 阶段自动注入 UploadMappingTask,国内 CI 需手动配置 serviceCredentialsFile(json 私钥)绕过 Google 登录。
- 合规点:Android 13 后必须声明 android.permission.POST_NOTIFICATIONS 才能弹出“崩溃反馈通知”;收集 ANDROID_ID 前需在隐私政策中明示,否则国内应用商店审核会被驳回。
答案
Crashlytics 在客户端分“捕获—落盘—上传”三步完成自动收集。
- 捕获:App 启动时 FirebaseCrashlytics.init 把 Java 默认异常处理器替换为 CrashlyticsUncaughtExceptionHandler;若集成 NDK,libcrashlytics.so 通过 sigaction 注册信号处理函数。
- 落盘:崩溃触发后,handler 在 50 ms 内把线程回溯、寄存器、maps、最近 64 KB 日志写进 /files/.com.google.firebase.crashlytics/files/v5/cls 开头的 mmap 文件,文件名带 UUID 确保唯一;写完立即 fsync 并关闭文件描述符,防止内核缓存丢失。
- 上传:进程重启后 ContentProvider 自动拉起 CrashlyticsInitProvider,WorkManager 调度 SendReportWorker,满足网络、电池、存储空间阈值后,通过 Firebase Installations 拿到的 fid+token 对 firebasecrashlytics.googleapis.com 发起 HTTPS POST,报文为 gzip 压缩的 protocol buffer,成功后删除本地 cls 文件。
- 设备信息:在首次启动时预先采集,随 crash 文件一起序列化,包括厂商、机型、Android 版本、ABI、内存、Root 状态、渠道号,确保崩溃时不再反射 Build 类,降低死锁风险。
- 还原:构建期 R8 生成 mapping.txt,CI 中执行 ./gradlew uploadCrashlyticsMappingRelease,把混淆规则上传到后台,堆栈里的 a.b.c 自动还原成真实类名、方法名、行号。
- 国内适配:无 GMS 设备直接走 HTTPS,需把 firebasecrashlytics.googleapis.com 加入网络白名单;上传任务受 WorkManager 的电源限制,可在调试阶段强制调用 FirebaseCrashlytics.getInstance().sendUnsentReports() 立即触发,方便 QA 验证。
拓展思考
- 如果崩溃发生在 fork 出的子进程,Signal Handler 如何写盘?
答:Crashlytics NDK 在子进程同样注册 handler,但写盘前会判断 getppid(),若父进程已死则把文件写到 /sdcard/Android/data/<pkg>/files/.crashlytics,重启后主进程再迁移,避免 SELinux 拒绝。 - 国内厂商后台冻结导致上传失败,如何兜底?
答:可在应用前台生命周期内再次调用 sendUnsentReports(),或者把 cls 文件加密后随业务埋点一起上传到自己的服务器,再由服务端转发到 Crashlytics 私有桶,实现“双通道”容灾。 - 合规升级:工信部 164 号文要求敏感信息境内存储,若企业把 Crashlytics 数据路由到 Google 新加坡节点,需如何改造?
答:使用 Firebase Data Residency 功能,把项目设置为“shanghai”区域,或在后台自建 Symbolicator,把 mapping.txt 与 cls 文件留在国内服务器,仅把脱敏后的堆栈摘要同步到 Firebase 控制台,既满足合规又保留符号化能力。