1. 泄漏现象识别
BroadcastReceiver 动态注册后未在生命周期结束时调用 unregisterReceiver(),会导致 Activity、Fragment 或 Service 实例被 Receiver 间接持有,无法被 GC 回收。这种泄漏在症状上表现为一系列可观测的系统行为异常,以下是四种核心表现及判断方法。
1.1 症状速查表
| 症状 | 典型表现 | 触发时机 | 优先级 |
|---|---|---|---|
| 内存抖动 (Memory Churn) | 频繁 GC 日志、Heap 曲线锯齿状波动 | 反复进入/退出页面 | 高 |
| OOM (OutOfMemoryError) | 应用崩溃,日志含 OutOfMemoryError | 长时间操作或大内存页面 | 极高 |
| GC 频繁 | Logcat 中 GC 日志间隔 < 1s,Allocation Tracker 显示大量 Receiver 对象 | 任何页面切换 | 中 |
| 系统拒绝注册 | 调用 registerReceiver() 抛出 IllegalArgumentException或 log 打印 ActivityManager: ... already registered | 重复注册同一 Receiver | 高 |
1.2 故障树:从现象到可疑泄漏点
当你在 Logcat / Profiler 中观察到上述任一症状,可按以下故障树逐层下探,快速定位是否由 BroadcastReceiver 泄漏引发:
排查优先级策略: 先检查
OOM + GC 频繁 组合(最易发现),再排查 内存抖动 或 系统拒绝注册(隐蔽性强)。
1.3 各症状详细识别方法
? 内存抖动 (Memory Churn)
- 定义: 短时间内大量对象创建和销毁,导致 Heap 使用曲线呈现频繁锯齿状。
- 观察工具: Android Studio Memory Profiler / Profile GPU / Perfetto。
- 典型数据: Heap 总量无明显增长但 GC 频率 > 5次/秒。
- 怀疑点: 每个页面 onResume 注册、onPause 未注销 → 每次进入创建新的 Receiver 实例,旧实例因被 Context 持有无法回收。
? OOM (OutOfMemoryError)
- 定义: Dalvik 堆无法分配更多内存,抛出
java.lang.OutOfMemoryError。 - 关键日志:
java.lang.OutOfMemoryError: Failed to allocate a 30498828 byte allocation with 10733472 free bytes and 10MB until OOM, target footprint 119080960, growth limit 268435456 - 排查切入点: Dumpsys meminfo 查看
Activities数量是否异常高(正常应 < 2)。
使用adb shell dumpsys meminfo| grep "Activities" - 泄漏程度: OOM 通常意味着泄漏已经持续较长时间,对象数量可达数百个。
? GC 频繁
- 定义: Logcat 中出现大量
I/art: GC或D/dalvikvm: GC_FOR_ALLOC,间隔 < 1 秒。 - 分析辅助: 使用
adb logcat -s "art"过滤 GC 日志,观察objects freed和pauses。 - 特征对比: 正常 App 在空闲时 GC 频率 < 0.5次/秒;若连续 10 秒内出现 15 次以上 GC,说明有瞬时对象泄漏或内存碎片。
? 系统拒绝注册
- 现象: 调用
registerReceiver(receiver, filter)时抛出IllegalArgumentException: Receiver not registered: ...或 log 显示ActivityManager: ... already registered。 - 典型场景: 在 onResume 中注册,但未在 onPause 中注销;再次进入时试图注册同一个已注册的 Receiver 实例。
- 避坑: 系统不允许重复注册同一个 BroadcastReceiver 对象(即使 filter 不同)。
正确做法:每次注册前先 unregister,或使用registerReceiver前加 try-catch。
‼️ 常见误解: “我调用了 unregisterReceiver 就不会泄漏。” 实际泄漏往往发生在未调用的分支(如异常退出、嵌套 fragment 未正确处理)。务必在所有生命周期终点(onPause、onStop、onDetach、onDestroyView)都做防御性注销。
1.4 快速自查清单
当你的 App 出现上述任一症状时,先对照以下清单做初步判断:
- Heap 锯齿? → 打开 Memory Profiler,观察 5 分钟内 Heap 曲线是否持续锯齿,且 GC 事件密集。
- Logcat 搜 “Receiver not registered”? → 若出现,至少有一个页面的注册和注销不配对。
- Dumpsys 看 Activity count:
adb shell dumpsys meminfo your.package | findstr "Activities",正常值 ≤ 2;若 > 5 且界面已关闭,高度怀疑 Activity 泄漏(Receiver 是常见帮凶)。 - Profile 检查 Receiver 对象数量: 在 Profiler 中搜索
BroadcastReceiver子类,看存活数量是否与页面打开次数成正比。
1.5 下一步行动
识别症状只是第一步。第 2 章将深入数据采集,教你如何用 Heap Dump、Allocation Tracker 和 Dumpsys 精准捕获泄漏证据。本章的故障树和自查清单将作为后续排查的入口参考。
? 核心原则: 所有泄漏现象都是结果,而非原因。不要停留在“GC 频繁”的表面,立即开始采集数据定位具体泄漏点。