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 频繁 组合(最易发现),再排查 内存抖动 或 系统拒绝注册(隐蔽性强)。
BroadcastReceiver泄漏症状故障树 BroadcastReceiver泄漏症状 内存抖动 (Churn) 频繁GC/锯齿Heap OOM (OutOfMemory) 应用崩溃 GC频繁 日志间隔‎<1s 系统拒绝注册 IllegalArgumentException 已销毁页面未 unregister 重复注册同 一个Receiver ? 判断原则 单一症状可能由多种原因引起(如图片缓存也导致GC频繁) 当“内存抖动 + GC频繁 + 系统拒绝注册”同时出现时,泄漏概率 > 90%

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 出现上述任一症状时,先对照以下清单做初步判断:

  1. Heap 锯齿? → 打开 Memory Profiler,观察 5 分钟内 Heap 曲线是否持续锯齿,且 GC 事件密集。
  2. Logcat 搜 “Receiver not registered”? → 若出现,至少有一个页面的注册和注销不配对。
  3. Dumpsys 看 Activity count: adb shell dumpsys meminfo your.package | findstr "Activities",正常值 ≤ 2;若 > 5 且界面已关闭,高度怀疑 Activity 泄漏(Receiver 是常见帮凶)。
  4. Profile 检查 Receiver 对象数量: 在 Profiler 中搜索 BroadcastReceiver 子类,看存活数量是否与页面打开次数成正比。

1.5 下一步行动

识别症状只是第一步。第 2 章将深入数据采集,教你如何用 Heap Dump、Allocation Tracker 和 Dumpsys 精准捕获泄漏证据。本章的故障树和自查清单将作为后续排查的入口参考。

? 核心原则: 所有泄漏现象都是结果,而非原因。不要停留在“GC 频繁”的表面,立即开始采集数据定位具体泄漏点。