Skip to content

memory-leak-demo ​

1. 实验目标 ​

用最小 Android 实现复现典型内存泄漏并接入 LeakCanary 自动检测(对应 docs/02-memory-lifecycle/06):

  • 四种经典泄漏场景:静态字段持有 Activity Context、匿名 Handler 发延迟消息、进程级单例缓存 View、监听器只注册不注销
  • LeakCanary 零代码接入:debugImplementation 一行依赖即自动监控 onDestroy 后仍被持有的 Activity
  • 泄漏 trace 解读入口:从通知栏 / Logcat 进入泄漏报告,看「最短强引用路径 to GC Root」与每跳的 Leaking: 判定

错误示范页 MemoryLeakDemoActivity.kt 与正确写法对照页 MemoryLeakFixedActivity.kt 一一对应,勿把错误示范复制进生产代码。

2. 工程要点与依赖接入 ​

  • LeakCanary 接入(app 模块统一维护,见 app/build.gradle.kts):

    kotlin
    // debugImplementation:LeakCanary 只应跑在 debug 构建(官方推荐接法)
    debugImplementation(com.squareup.leakcanary:leakcanary-android:2.14)

    版本来源:Maven Central com.squareup.leakcanary:leakcanary-android:2.14(2024-04 发布,当前最新稳定版;官方 Getting Started 同版本)。无需任何初始化代码——库通过 ContentProvider 自动安装 ActivityDestroyWatcher。

  • 单例容器:LeakSingletonHolder.kt 模拟「与进程同寿命的全局管理器」,承载场景③④的泄漏引用

  • 正确写法三件套:Kotlin 嵌套类(非 inner,等价 Java 静态内部类)+ WeakReference 兜底 + onDestroy 里 removeCallbacksAndMessages(null) 与监听器成对注销

3. 运行方式 ​

bash
cd labs/android/host
./gradlew installDebug

设备/模拟器打开 Android Host → 打开 内存泄漏复现与 LeakCanary 实验室。

验证步骤:

  1. 进入错误示范页,点任一场景按钮(①~④)建立泄漏引用
  2. 按系统返回键销毁本页(Logcat 可见本页 onDestroy 已执行)
  3. 等待约 5 秒:通知栏出现 LeakCanary 泄漏报告;点开可见堆转储分析结果
  4. 对照:进入「正确写法对照页」重复同样操作,LeakCanary 保持沉默

4. 预期现象与观察重点 ​

注意:以下输出为来自官方文档/历史真机观察的预期输出(本次改动仅完成编译验证,未在真机实跑),实际文案随设备与版本略有差异。

4.1 Logcat(过滤 tag: LeakCanary) ​

text
LeakCanary: Found 1 ObjectWatcher created objects that are no longer strongly reachable... 
LeakCanary: Found 1 retained objects, ...
LeakCanary: Analysis result for ...MemoryLeakDemoActivity: 1 APPLICATION LEAK

4.2 泄漏引用链(场景①静态字段的典型形态) ​

text
┬───
│ GC Root: System class
│
├─ android.app.LoadedApk class
│    Leaking: NO (a class is never leaking)
...
├─ com.shayn.engineeringreviewlab.androidhost.topics.memory_lifecycle.
│     memory_leak_demo.MemoryLeakDemoActivity companion object
│    ↓ static MemoryLeakDemoActivity.leakedContext
│                                  ~~~~~~~~~~~~~~
├─ com.shayn...MemoryLeakDemoActivity instance
│    Leaking: YES (ObjectWatcher was watching this because it received
│           Activity#onDestroy() callback and Activity not weakly reachable)
├─ ...

观察重点 ​

  • 报告标题里的 APPLICATION LEAK(应用代码可修)vs LIBRARY LEAK / UNREACHABLE
  • 引用链每一跳的 Leaking: NO → YES 翻转点:翻转处上方一行就是该修的引用
  • 下划线标注的字段名(如 leakedContext)即切断强引用的目标
  • 正确写法对照页同样操作后无任何报告——验证「嵌套类 + WeakReference + onDestroy 收口」

5. 常见误区 ​

误区实际情况
LeakCanary 需要手动 init不需要——通过 ContentProvider 自动安装 watcher;release 构建不要引入
未入队 = 确定泄漏弱引用未进入 ReferenceQueue 也可能只是 GC 尚未完成,LeakCanary 会继续观察并最终用堆转储确认
WeakReference 是修复手段它只是 Handler 场景的兜底;根治靠生命周期收口(removeCallbacks / unregister),弱引用不能替代治理
单例持 View 无害View 自身持有创建它的 Activity Context,等价于单例持 Activity
内存没降就是泄漏onDestroy ≠ 立即可回收;判定要结合「合理时间后仍可达 + 找到强引用链」(详见文档 §1 三步判断法)

6. 对应知识库文档 ​

7. 关键源码 ​

站点构建时间:2026/8/24 23:43:17