Appearance
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 实验室。
验证步骤:
- 进入错误示范页,点任一场景按钮(①~④)建立泄漏引用
- 按系统返回键销毁本页(Logcat 可见本页
onDestroy已执行) - 等待约 5 秒:通知栏出现 LeakCanary 泄漏报告;点开可见堆转储分析结果
- 对照:进入「正确写法对照页」重复同样操作,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 LEAK4.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. 关键源码
- MemoryLeakDemoActivity.kt — 四种泄漏场景的错误示范
- MemoryLeakFixedActivity.kt — 正确写法对照(SafeHandler + 成对注销)
- LeakSingletonHolder.kt — 进程级单例容器(泄漏引用的持有方)