1. LeakCanary基本使用介绍
Facebook 曾报告他们的 Android 应用中有 30% 的崩溃是由 OOM 引起的。而 LeakCanary 作为 Square 公司开源的一款内存泄漏检测工具
1.1 使用举例
使用案例:1.Android 内存泄露实战之自定义view 揭秘LeakCanary+MAT+Profile全链路深度解剖自定义View泄漏 - 掘金
LeakCanary的集成和使用可以分为三个主要步骤:
步骤一:添加依赖 在模块的build.gradle文件中添加依赖:
gradle
dependencies {
// 核心库
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
// 可选:多进程分析支持
debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.9.1'
// 可选:与App Startup集成
debugImplementation 'com.squareup.leakcanary:leakcanary-android-startup:2.9.1'
}
步骤二:自动检测场景 LeakCanary会自动检测以下场景的内存泄漏:
- Activity的onDestroy()后被持有
- Fragment的onDestroy()后被持有
- ViewModel的onCleared()后被持有
- Service的onDestroy()后被持有
- RootView从WindowManager移除后被持有
步骤三:查看泄漏报告 当检测到内存泄漏时,LeakCanary会:
- 在通知栏显示警告
- 在Logcat输出详细日志
- 在应用内生成可视化报告
- 在桌面创建快捷入口(可随时查看历史泄漏)
1.2 自动初始化原理
LeakCanary 2.0的自动初始化机制基于Android的ContentProvider特性:
关键实现细节:
- 在库的AndroidManifest.xml中声明:
xml
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
- 初始化逻辑:
kotlin
internal class MainProcessAppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
// ...其他方法空实现...
}
优势:
- 完全无侵入:无需修改应用代码
- 自动适配多进程:只在主进程初始化
- 灵活配置:可通过资源文件禁用自动初始化
2. LeakCanary核心原理
LeakCanary 的核心也是解决内存问题常见的三板斧:监控泄漏、采集镜像、分析镜像。
lua
+---------------------+ +---------------------+
| 监控模块 | | 分析服务 |
| - ActivityTracker |------>| - HeapDumper |
| - FragmentTracker | | - HeapAnalyzer |
| - ViewModelTracker | +----------+----------+
+----------+----------+ |
| |
v v
+----------+----------+ +----------+----------+
| 内存触发检测 | | 后台处理进程 |
| - ObjectWatcher | | - AnalysisService |
| - ReferenceQueue | | - Shark (Hprof Parser)|
+---------------------+ +---------------------+
2.1 监控泄漏
lua
+-------------------------------------------------------------------------------------------+
| 应用主进程 |
| |
| +---------------------+ +---------------------+ +---------------------+ |
| | 监控对象注册 | | 泄漏检测触发 | | 泄漏判定 | |
| | - ActivityWatcher | → | - ObjectWatcher | → | - KeyedWeakReference| |
| | - FragmentWatcher | | - checkRetained() | | - ReferenceQueue | |
| | - ViewModelWatcher | +---------------------+ +----------+----------+ |
| | - ServiceWatcher | | | |
| | - RootViewWatcher | | 5秒延迟检查 | GC事件 |
| +---------------------+ v v |
| +---------------------+ +---------------------+ |
| | 泄漏确认 | | 弱引用处理 | |
| | - moveToRetained() | ← | - queue.poll() | |
| | - onObjectRetained | +---------------------+ |
| +---------------------+ |
| | |
| v |
| +---------------------+ |
| | 触发Heap Dump | |
| | - HeapDumpTrigger | |
| +---------------------+ |
| |
+-------------------------------------------------------------------------------------------+
核心类:ObjectWatcher
scss
// ObjectWatcher.java
public void watch(Object watchedObject, String description) {
// 创建带Key的弱引用
KeyedWeakReference weakReference = new KeyedWeakReference(
watchedObject,
description,
referenceQueue, // 关联引用队列
UUID.randomUUID().toString()
);
// 延迟检查
checkRetainedExecutor.execute(() -> {
moveToRetainedIfNecessary(weakReference);
});
}
private void moveToRetainedIfNecessary(KeyedWeakReference reference) {
if (gone(reference)) {
return; // 对象已回收
}
retainedReferences.add(reference);
onObjectRetained(); // 触发泄漏处理
}
private boolean gone(KeyedWeakReference reference) {
// 从引用队列中取出已被回收的引用
KeyedWeakReference refPoll;
while ((refPoll = (KeyedWeakReference) referenceQueue.poll()) != null) {
retainedReferences.remove(refPoll);
}
return !retainedReferences.contains(reference);
}


2.1 监控泄漏:引用+引用队列
2.1.1 双重判定机制
完整判定流程:
延迟5秒的三大原因:
- 等待GC执行:
kotlin
object Default : GcTrigger {
override fun runGc() {
// 只是建议GC,不保证立即执行
Runtime.getRuntime().gc()
// 等待GC线程工作
Thread.sleep(100)
// 处理finalize队列
System.runFinalization()
}
}
- 过滤临时引用:
- 异步任务可能短暂持有对象
- 动画未完成时View被保留
- 跨方法调用的中间状态
- 平衡准确性与时效性:
- 实测数据表明5秒能捕获95%的真实泄漏
- 更长时间收益递减但用户体验下降
2.1.2 Leakcanary会在onDestory方法中进行2次GC
2.1.3 为什么要延迟5秒后确认
2.1.4 误报情况及处理
常见误报场景:
-
Finalizer队列延迟:
- 对象已无强引用但仍在finalize队列
- 解决方案:增加GC触发次数
-
系统服务缓存:
- 如InputMethodManager的缓存
- 解决方案:配置AndroidReferenceMatchers
-
检测期间新引用:
- 监控期间业务代码新建引用
- 解决方案:延长延迟时间或代码审查
误报处理API:
kotlin
LeakCanary.config = LeakCanary.config.copy(
referenceMatchers = AndroidReferenceMatchers.appDefaults +
// 忽略系统已知"假泄漏"
IgnoredReferenceMatcher(
pattern = "com.android.internal.util.ConcurrentUtils"
)
)
2.2 采集镜像
当怀疑有内存泄漏时:
- 触发GC尝试回收对象
- 如果对象仍然存活,dump内存堆
- 生成.hprof文件存储到文件系统
lua
+-------------------------------------------------------------------------------------------+
| 应用主进程 |
| |
| +---------------------+ +---------------------+ +---------------------+ |
| | 泄漏阈值检查 | | GC触发 | | Heap Dump执行 | |
| | - HeapDumpTrigger | → | - GcTrigger | → | - AndroidHeapDumper | |
| | - checkRetained() | | - runGc() | | - Debug.dumpHprof() | |
| +----------+----------+ +----------+----------+ +----------+----------+ |
| | | | |
| | 泄漏对象≥阈值 | 确保对象未被GC | 生成.hprof文件 |
| v v v |
| +---------------------+ +---------------------+ +---------------------+ |
| | 状态判断 | | 二次泄漏确认 | | 文件存储 | |
| | - appVisible | | - retainedCount | | - LeakDirectory | |
| | - lastDumpTime | | - removeWeakRefs | | - maxStoredDumps | |
| +---------------------+ +---------------------+ +---------------------+ |
| |
+-------------------------------------------------------------------------------------------+

核心类:InternalLeakCanary
scss
// InternalLeakCanary.java
@Override
public void onObjectRetained() {
if (checkRetainedCount()) {
dumpHeap();
}
}
private File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
// 调用Android原生API生成hprof文件
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
return heapDumpFile;
}
Heap Dump完整流程:
关键参数配置:
kotlin
LeakCanary.config = LeakCanary.config.copy(
dumpHeap = true, // 是否启用Heap Dump
dumpHeapWhenDebugging = false, // 调试时是否Dump
retainedVisibleThreshold = 5, // 可见时的阈值
requestWriteExternalStoragePermission = true, // 存储权限
maxStoredHeapDumps = 7 // 最大保存数量
)
2.3 分析镜像
使用Shark库分析内存堆:
- 查找泄漏对象的引用链
- 排除系统已知的"非泄漏"情况
- 确定最短引用路径

核心类:HeapAnalyzerService
scss
// HeapAnalyzerService.java
public HeapAnalysis analyzeHeap(File heapDumpFile) {
try {
// 使用Shark解析hprof
HeapGraph graph = HprofHeapGraph.open(heapDumpFile);
// 查找泄漏对象
List<LeakingObject> leakingObjects = findLeakingObjects(graph);
// 构建泄漏路径
List<LeakTrace> leakTraces = new ArrayList<>();
for (LeakingObject leakingObject : leakingObjects) {
leakTraces.add(findPathToGcRoot(graph, leakingObject));
}
return new HeapAnalysisSuccess(heapDumpFile, leakTraces);
} catch (Exception e) {
return new HeapAnalysisFailure(heapDumpFile, e);
}
}

2.3.1 分析进程与线程
多策略执行模型:
策略选择逻辑:
kotlin
when {
RemoteWorkManagerHeapAnalyzer.canUseRemoteService ->
RemoteWorkManagerHeapAnalyzer
WorkManagerHeapAnalyzer.canUseWorkManager ->
WorkManagerHeapAnalyzer
else ->
BackgroundThreadHeapAnalyzer
}
2.3.2 Shark分析hprof流程
使用Shark替代HAHA,解析速度提升10倍
Shark解析引擎工作流程:
性能优化点:
- 内存映射文件:使用MappedByteBuffer避免全量加载
- 并行解析:多线程处理不同数据段
- 缓存复用:相同hprof的重复分析复用部分结果
如何GC ROOT, 可以通过BFS算法:
2.3.3 GC Root 剪枝,可达性分析是从 GC Root 自顶向下 BFS
2.3.4 泄漏聚类统计
聚类算法实现:
kotlin
fun groupLeaks(leaks: List<LeakTrace>): Map<String, LeakGroup> {
return leaks.groupBy { leak ->
// 对引用链关键特征取哈希
sha1Hash(leak.uniqueString())
}.mapValues { (_, group) ->
LeakGroup(
signature = group.first().signature,
leaks = group,
createdAt = System.currentTimeMillis()
)
}
}
聚类效果示例:
yaml
Leak Similarity Report:
┌─────────────────┬─────────┐
│ Signature │ Count │
├─────────────────┼─────────┤
│ a1b2c3... │ 15 │ ← 相同泄漏发生15次
│ d4e5f6... │ 3 │ ← 另一种泄漏
└─────────────────┴─────────┘
lua
+-----------------------------------------------------------------------+
| 分析服务进程 (:leakcanary) |
| |
| +---------------------+ +---------------------+ |
| | HeapAnalyzerService | Shark 解析引擎 |
| | - 启动分析任务 | ----->| - HprofHeapGraph | |
| | - 发送进度事件 | | - PathFinder | |
| +----------+----------+ +----------+----------+ |
| | | |
| v v |
| +----------+----------+ +----------+----------+ |
| | HeapAnalysis | | LeakTrace | |
| | - 解析结果封装 | | - 引用链构建 | |
| +----------+----------+ +----------+----------+ |
| | |
| v |
| +----------+----------+ |
| | AnalysisResult | |
| | - 泄漏分类 | |
| | - 报告生成 | |
| +---------------------+ |
| |
+-----------------------------------------------------------------------+
2.3.4 核心算法应用
算法 | 应用场景 | 实现类 | 优化点 |
---|---|---|---|
BFS | 引用链搜索 | PathFinder | 优先搜索最短路径 |
支配树 | 计算对象保留大小 | DominatorTree | 增量计算 |
哈希聚类 | 相似泄漏合并 | HeapAnalyzer | 特征值提取优化 |
LRU缓存 | 解析结果缓存 | HprofInMemoryCache | 智能内存管理 |
2.3.5 LeakCanary 分析的时机?
ObjectWatcher 判定被监控对象发生泄漏后,会通过接口方法 OnObjectRetainedListener#onObjectRetained()
回调到 LeakCanary 内部的管理器 InternalLeakCanary 处理(在前文 AppWatcher 初始化中提到过)。LeakCanary 不会每次发现内存泄漏对象都进行分析工作,而会进行两个拦截:
- 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值;
- 拦截 2:计算距离上一次 HeapDump 未超过 60s。
2. 4 LeakCanary原理总结:
2.4.1 详细总结
1.初始化:直接debugImplementation就能实现,他是在 ContentProvider里面做的初始化,当打包的时候,会合并各个清单文件。里面注册的 ContentProvider,ContentProvider 会在 Application的 attachBaseContext 之后, onCreate之前创建。在ContentProvider的出事时候,
- 在Application里面注册监控所以activitity的生命周期,注册 Application.ActivityLifecycleCallbacks 监听Activity的生命周期,以及 fragmentManager.registerFragmentLifecycleCallbacks监听Fragment的生命周期
3.ondestroy 调用RefWatcher的watch(),比如监听 onActivityDestroyed方法,当监听到这个方法调用的时候,把Activity 全部放到观察数组中,并且用引用队列包裹这个activity,生成key(UUID),然后过5s,看看引用队列里面有没有这个key,如果有,证明回收了,然后把观察数组中的remove掉这个key,此时如果这个数组里面的count > 0 ,证明有可能是怀疑的泄漏,然后 调用 Runtime.getRuntime().gc() ,之后再 看看 引用队列有没有这个数据,如果有,然后把观察数组中的remove掉这个key,之后再看观察数组中的count,如果小于5,只是提示一下。如果count 大于 5 (防止卡顿),就开始使用 shark (2.0之前是haha)分析堆栈信息。用可达到性分析,找到最短的链路,
4.然后在后台线程检查引用是否被清除,如果没有,调用GC。
5.如果引用还是未被清除,dump ,然后生成一个.hprof文件,内存快照
6.开一个服务和线程去分析和解析这个profier文件
7.计算 到 GC roots 的最短强引用路径(在ui会显示),并确定是否是泄露。如果是的话,建立导致泄露的引用链。
2.4.2 归纳总结
:
1.LeakCanary通过ApplicationContext统一注册监听的方式,通过application.registerActivityLifecycleCallbacks来绑定Activity生命周期的监听,从而监控所有Activity; (监控生命周期)
2.在Activity执行onDestroy时,开始检测当前页面是否存在内存泄漏,并分析结果。(触发入口)
3.KeyedWeakReference与ReferenceQueue联合使用,在弱引用关联的对象被回收后,会将引用添加到ReferenceQueue;清空后,可以根据是否继续含有该引用来判定是否被回收;判定回收 (真正的原理)
4.手动GC, 再次判定回收,采用双重判定来确保当前引用是否被回收的状态正确性;如果两次都未回收,则确定为泄漏对象。(确保一直性)
5.dump信息,得到hprof文件
6.开进程分析文件,得到引用关系链
2.4.3 简单总结
:
1.contentprovider启动
2.注册生命周期,在activity和fragment结束的时候会进行检测
3.引用队列RefrenceQueue,5s有没有被清除。手动调用GC,Runtime
4.找到有问题的引用,开始分析(可达分析),是否是真正的内存泄露
3. 架构图
3.1 以Activity为例子的泄漏,整体的流程
lua
+-----------------------------------------------------------------------+
| Android Application |
| |
| +---------------------+ +---------------------+ |
| | 监控模块 | | 分析服务 | |
| | - ActivityTracker |------>| - HeapDumper | |
| | | | - HeapAnalyzer | |
| | +----------+----------+ |
| +----------+----------+ | |
| | | |
| v v |
| +----------+----------+ +----------+----------+ |
| | 内存触发检测 | | 后台处理进程 | |
| | - ObjectWatcher | | - AnalysisService | |
| | - ReferenceQueue | | - Shark (Hprof Parser)| |
| +---------------------+ +---------------------+ |
| |
+-----------------------------------------------------------------------+
通用流程:
scss
+-------------------+ +-------------------+ +-------------------+
| ObjectWatcher | → | HeapDumper | → | HeapAnalyzerService
| - watch() | | - dumpHprofData()| | - Shark.parse() |
| - checkRetained()| +-------------------+ | - findPath() |
+-------------------+ +-------------------+
↑ ↓
+-------------------+ +-------------------+
| ReferenceQueue | | LeakTraceReport |
| - poll() | | - print() |
+-------------------+ +-------------------+
3.2 设计模式应用
七大核心模式解析:
3.2.1. 观察者模式(Observer Pattern)
应用场景:监控对象生命周期事件和泄漏状态变化
-
核心实现:
ObjectWatcher
作为被观察者,通过OnObjectRetainedListener
接口通知观察者(如HeapDumpTrigger
)泄漏事件。LeakCanary
的事件总线使用EventListener
列表处理分析进度和结果通知。
kotlin// ObjectWatcher.kt interface OnObjectRetainedListener { fun onObjectRetained() } class ObjectWatcher { private val listeners = mutableListOf<OnObjectRetainedListener>() fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) { listeners.add(listener) } private fun notifyListeners() { listeners.forEach { it.onObjectRetained() } } }
3.2.2. 构建者模式(Builder Pattern)
应用场景:灵活配置 LeakCanary
-
核心实现:
LeakCanary.Config
通过newBuilder()
提供链式配置API。
scss// LeakCanary.kt val config = LeakCanary.config.newBuilder() .retainedVisibleThreshold(3) .dumpHeapWhenDebugging(true) .build()
3.2.3. 策略模式(Strategy Pattern)
应用场景:动态切换算法实现
-
核心实现:
GcTrigger
提供不同的GC策略(如默认实现和测试用的无操作实现)。HeapDumper
可替换为第三方实现(如快手KOOM的fork模式)。
kotlin// GcTrigger.kt interface GcTrigger { fun runGc() object Default : GcTrigger { /* 默认实现 */ } object NoOp : GcTrigger { /* 测试用空实现 */ } }
3.2.4. 工厂方法模式(Factory Method Pattern)
应用场景:创建复杂对象
-
核心实现:
LeakDirectoryProvider
封装.hprof文件的创建逻辑。Shark
中的HeapAnalyzer
根据配置选择不同的分析策略。
kotlin// LeakDirectoryProvider.kt fun newHeapDumpFile(): File { return File(leakDirectory, "heap_dump_${timestamp}.hprof") }
3.2.5. 门面模式(Facade Pattern)
应用场景:简化复杂子系统调用
-
核心实现:
AppWatcher
封装所有Watcher的安装/卸载操作。LeakCanary
类提供全局快捷入口方法(如LeakCanary.showLeakDisplayActivity()
)。
-
源码示例:
kotlin// AppWatcher.kt object AppWatcher { fun manualInstall(application: Application) { ActivityWatcher(application, objectWatcher).install() FragmentWatcher(application, objectWatcher).install() // 其他Watcher安装... } }
3.2.6. 责任链模式(Chain of Responsibility)
应用场景:泄漏对象分析流程
-
核心实现:
ObjectInspectors
链式检查对象状态(如判断是否系统泄漏)。
scss// AndroidObjectInspectors.kt val appDefaults = listOf( ViewInspector, FragmentInspector, RootViewInspector, // 更多检查器... ) // 调用链 inspectors.forEach { inspector -> inspector.inspect(reporter) }
3.2.7. 代理模式(Proxy Pattern)
应用场景:Hook系统服务
-
核心实现:
ServiceWatcher
动态代理AMS的IActivityManager
接口,监听Service销毁事件。
kotlin// ServiceWatcher.kt private fun swapActivityManager(proxy: (Any) -> Any) { val iActivityManager = getIActivityManager() val proxyInstance = proxy(iActivityManager) setIActivityManager(proxyInstance) // 替换原始实例 }
3.2 核心类关系
完整类图:
4. 优缺点与改进
4.1 不能用于线上的原因
四大核心限制:
4.1.1. 性能影响与卡顿问题
-
GC 触发卡顿:主动触发 GC 可能导致主线程阻塞(约 100-200ms),影响用户体验。
-
Dump 冻结应用 :
Debug.dumpHprofData()
会挂起所有线程(包括主线程),冻结时间与堆内存大小相关(通常 5-15秒),期间应用无响应。- 原因:Dump 需冻结 JVM 确保堆内存一致性,避免并发修改导致数据损坏。
-
分析过程资源占用:Shark 引擎分析 HPROF 文件时占用较高 CPU 和内存,可能引发 OOM(尤其低端设备)。
4.1.2. 准确性与误报问题
-
非确定性检测:基于多次泄漏(默认 5 次)的启发式判断,可能漏报(系统缓存 Activity)或误报(短周期对象)。
- 三星等机型适配:需额外策略(如检测 3 次泄漏 + 跨越 5 个新 Activity)排除系统缓存干扰。
-
依赖 HPROF 局限性:无法检测 Native 内存泄漏或非堆内存问题(如 Bitmap 像素数据)。
4.1.3. 自动化与协作短板
-
流程耦合:检测与分析绑定在同一进程,不适合 CI/CD 流水线。
-
解决方案(如腾讯 ResourceCanary):
- 分离检测与分析:客户端仅生成 HPROF,服务端离线分析。
- HPROF 裁剪:移除无关数据(保留类/对象引用信息),体积减少 90%。
-
-
问题消费效率低:
- 缺乏泄漏优先级评估(如高频泄漏 vs 低频泄漏)。
- 无 ROI 量化工具(如修复后内存节省量、卡顿减少比例)。
4.1.4. 数据风险:
4.2 线上改造方案
轻量级监控架构:
关键改造点:
-
替换Heap Dump:
kotlinclass LightweightWatcher : ReachabilityWatcher { override fun expectReachable(obj: Any, description: String) { // 只记录不Dump reportToServer(obj.javaClass.name, description) } }
-
增强过滤:
- 忽略系统类
- 要求连续多次检测到才上报
- 排除短生命周期对象
-
动态配置:
json{ "sampling_rate": 0.1, "threshold": 3, "exclude": ["android.", "com.google."] }
4.3 改造方案对比
ResourceCanary : 腾讯
KOOM: 快手
改进方案对比
方案 | 优化点 | 适用场景 |
---|---|---|
ResourceCanary | 分离检测/分析,裁剪 HPROF | 腾讯系自动化测试 |
KOOM | 子进程 Dump,冻结时间缩短至 1s 内 | 高实时性要求(如抖音) |
Android Profiler | 官方工具,支持 Native 内存分析 | 开发阶段深度调试 |
KOOM核心代码片段:
cpp
void forkAndDump() {
pid_t pid = fork();
if (pid == 0) { // 子进程
Debug.dumpHprofData(path);
exit(0);
} else { // 父进程
waitpid(pid, NULL, 0);
}
}
5. 常见泄漏案例
5.1 悬浮窗泄漏检测(重点)
kotlin
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.os.IBinder
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.Button
class FloatingService : Service() {
private lateinit var windowManager: WindowManager
private lateinit var floatingView: View
override fun onCreate() {
super.onCreate()
showFloatingWindow()
}
private fun showFloatingWindow() {
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val layoutInflater = LayoutInflater.from(this)
floatingView = layoutInflater.inflate(R.layout.floating_window, null)
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.TOP or Gravity.START
x = 100
y = 100
}
windowManager.addView(floatingView, params)
val closeButton = floatingView.findViewById<Button>(R.id.closeButton)
closeButton.setOnClickListener {
stopSelf() // 停止 Service,但可能不会正确移除 View
}
}
override fun onDestroy() {
super.onDestroy()
// ❌ 这里故意不调用 windowManager.removeView(floatingView),导致内存泄漏
}
override fun onBind(intent: Intent?): IBinder? = null
}
监控原理
LeakCanary 通过 RootViewWatcher
监控所有 已从 WindowManager 移除但未被回收的 View(包括悬浮窗)。核心逻辑:
- Hook 机制 :监听
WindowManagerGlobal
的mViews
列表变化。 - 检测时机 :当 View 从 Window 移除时(
onDetachedFromWindow()
),将其加入监控队列。
kotlin
// RootViewWatcher.kt
override fun install() {
Curtains.onRootViewsChangedListeners += listener
}
private val listener = OnRootViewAddedListener { rootView ->
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(v: View) {
objectWatcher.watch(v, "View detached from window")
}
})
}
悬浮窗的案例:
完整检测流程:
- 制造泄漏:
kotlin
class LeakyFloatWindow(context: Context) {
// 持有Activity context
private val windowManager = context.getSystemService<WindowManager>()!!
fun show() {
windowManager.addView(MyView(context), params)
}
// 故意不实现removeView
}
- LeakCanary报告分析:
ini
泄漏链:
┬───
│ GC Root: System Class (WindowManagerGlobal)
│
├─ android.view.WindowManagerGlobal
│ ↓ mViews (ArrayList)
├─ java.util.ArrayList
│ ↓ elementData (Object[])
├─ java.lang.Object[] array
│ ↓ [0] (MyView)
├─ com.example.MyView
│ ↓ mContext (Activity)
╰→ com.example.MainActivity
- 修复方案对比:
错误修复:
kotlin
// 仅移除View不够
windowManager.removeView(view)
正确修复:
kotlin
// 1. 使用Application Context
private val appContext = context.applicationContext
// 2. 确保移除
fun dismiss() {
windowManager.removeView(view)
view = null // 清除引用
}
// 3. 生命周期绑定
override fun onDestroy() {
dismiss()
}
6. 总结

LeakCanary的核心价值矩阵:
维度 | 实现方案 | 效果提升 |
---|---|---|
准确性 | 引用链分析+聚类 | 精准定位泄漏点 |
性能 | 多进程分析+Shark优化 | 分析速度提升10倍 |
易用性 | 自动初始化+可视化报告 | 接入成本降低90% |
扩展性 | 模块化设计+配置API | 支持5种扩展点 |
最佳实践路线图:
rust
journey
title LeakCanary使用路线
section 开发阶段
全量启用 --> 及时修复 --> 代码审查
section 测试阶段
CI集成 --> 自动化检测 --> 报告归档
section 线上阶段
轻量监控 --> 采样上报 --> 精准回捞
通过本文的全方位解析,开发者可以深入理解LeakCanary的工作原理,掌握其高级用法,并能够根据实际需求进行定制化改造,构建完整的内存泄漏防控体系。