Android内存泄漏检测实战:LeakCanary原理、集成与最佳实践

📅 2026/8/1 19:09:31
Android内存泄漏检测实战:LeakCanary原理、集成与最佳实践
1. 从“内存泄漏”到“内存溢出”一个Android开发者的日常烦恼做Android开发这些年最怕的不是需求变更也不是产品经理的奇思妙想而是半夜被报警电话叫醒告诉你线上App的崩溃率又飙升了。很多时候你打开崩溃日志一看十有八九是OutOfMemoryError。这时候你心里大概就明白了又是内存泄漏在作祟。内存泄漏和内存溢出这两个词经常被混用但它们其实是因果关系。你可以把App的内存想象成一个水池水池的容量是有限的比如系统分配给App的堆内存上限。内存泄漏就像是水池的某个水龙头没关紧一直在缓慢地滴水。短时间内水池的水位可能看不出明显变化但日积月累水总会漫出来——这就是内存溢出OOM。在Android开发里这个“水龙头”通常是一个本该被回收的对象因为被某个长生命周期的对象比如静态变量、单例、Activity的Context被非Activity持有意外地持有了引用导致垃圾回收器GC无法回收它。这个对象占用的内存就“泄漏”了再也回不到内存池中。手动排查内存泄漏过程极其痛苦。你需要复现可疑场景然后连接设备用Android Studio的Profiler工具抓取堆转储Heap Dump再在一堆看似杂乱无章的类实例和引用链中像侦探一样寻找那个“不该存在的引用”。这个过程耗时耗力而且严重依赖开发者的经验和直觉对于偶发性泄漏或者复杂引用链几乎就是大海捞针。所以当我在几年前第一次接触到LeakCanary时感觉就像在黑暗的房间里找到了一盏灯。它不是一个运行时监控工具而是一个“内存泄漏侦探”能在开发阶段、甚至是测试阶段自动帮你发现、分析并清晰地指出泄漏的根源。它的设计哲学非常直接与其在OOM发生后才去亡羊补牢不如在内存泄漏刚发生时就将它扼杀在摇篮里。简单来说LeakCanary是一个专门为Android设计的开源内存泄漏检测库。它通过监控Activity、Fragment、ViewModel、Service等组件的生命周期在它们本该被销毁如Activity的onDestroy()后主动触发一次GC然后检查这些对象是否还被强引用着。如果发现泄漏它会自动抓取堆转储进行分析并最终在通知栏和Logcat中给你一个清晰、直观的泄漏引用链报告直接告诉你“谁”持有了“谁”导致了泄漏。对于任何一位Android开发者无论是刚入门的新手还是经验丰富的老手将LeakCanary集成到项目中都应该成为开发流程中的标准动作。它不仅能帮你快速定位和修复内存问题更能通过持续的反馈培养你写出更健壮、内存更安全的代码的习惯。2. LeakCanary的核心工作原理它如何成为“内存侦探”很多开发者只是把LeakCanary当作一个“即插即用”的黑盒工具知道它能报警就完事了。但理解其背后的工作原理能让你更信任它的报告也能在它“误报”或“不报”时知道问题可能出在哪里。LeakCanary的侦探工作可以拆解为四个核心步骤监视、等待、快照、分析。2.1 第一步安装“监控探头”——对象监视器LeakCanary的监控不是漫无目的的。它主要关注那些具有明确生命周期的对象最典型的就是Activity和Fragment。在Android中这些组件的创建和销毁是有清晰路径的这为泄漏检测提供了完美的切入点。LeakCanary通过一个叫做ActivityLifecycleCallbacks的接口来全局监听所有Activity的生命周期。当你的App启动时LeakCanary会自动注册这个监听器。从此每一个Activity的创建onCreate、销毁onDestroy都尽在掌握。对于Fragment情况稍微复杂一些因为Fragment的生命周期依赖于其宿主Activity。LeakCanary通过注册FragmentManager.FragmentLifecycleCallbacks来监听Fragment的生命周期事件。当监听到一个Activity的onDestroy()方法被调用时LeakCanary并不会立刻断定它泄漏了。因为此时这个Activity对象可能还在被一些即将被回收的短生命周期对象引用着比如一个正在执行最后一行代码的Handler。立刻判定为泄漏会导致大量误报。2.2 第二步设置“观察期”——延迟与GC触发这是LeakCanary设计中最精妙的一环直接决定了检测的准确性。当一个被监视的对象比如Activity被销毁后LeakCanary会将它放入一个“待观察”的队列中并启动一个延迟。这个延迟时间默认是5秒。在这5秒内LeakCanary会主动触发一次垃圾回收GC。注意是“主动触发”。它通过调用Runtime.getRuntime().gc()并配合Thread.sleep(100)等方式尽力确保一次完整的GC发生。这一步至关重要目的是清除掉所有那些已经失去引用、只是暂时还没被GC线程回收的“垃圾”对象。5秒过后LeakCanary会去检查那个被销毁的Activity对象是否还存在。如何检查它使用了一个叫做KeyedWeakReference的弱引用来间接观察。在对象被放入观察队列时LeakCanary会创建一个指向该对象的KeyedWeakReference。弱引用的特点是它不会阻止其所指对象被垃圾回收器回收。5秒后LeakCanary会检查这个弱引用如果弱引用指向的对象已经变成null说明原对象已被GC回收一切正常没有泄漏。如果弱引用还能通过get()方法拿到非null的对象那么抱歉这个对象在经历了一次主动GC后依然存活说明有其他的强引用依然在持有它——内存泄漏实锤了。2.3 第三步收集“犯罪现场证据”——堆转储与分析一旦确认泄漏发生LeakCanary就会启动它的“刑侦”流程。首先它会使用Android系统提供的Debug.dumpHprofData()方法抓取当前App进程的堆转储文件HPROF文件。这个文件可以理解为当前时刻Java堆内存中所有对象及其引用关系的一个完整快照。拿到这个“犯罪现场”的快照后LeakCanary会启动一个独立的分析进程默认是:leakcanary进程在这个进程里使用Square公司另一个强大的库——SharkLeakCanary 2.0之后的分析引擎来解析HPROF文件。Shark会在这个巨大的对象图中寻找那条从“垃圾回收根节点”GC Root到泄漏对象的引用路径。为什么是GC Root在Java中GC Root是一类特殊的对象它们是垃圾回收的起点。常见的GC Root包括静态变量staticfields活跃的Java线程ThreadJNI局部和全局引用系统Class Loader加载的类泄漏对象一定存在一条从某个GC Root出发的引用链。Shark引擎的工作就是找到这条最短的、最有意义的引用链。2.4 第四步出具“侦探报告”——可视化泄漏链分析完成后LeakCanary不会给你一堆晦涩难懂的十六进制地址和类名。它会生成一份极其友好的报告。这份报告通常以两个渠道呈现通知栏提醒你的手机或模拟器通知栏会立即出现一个通知告诉你发现了N个内存泄漏。点击它可以跳转到LeakCanary内置的展示界面。Logcat输出在Android Studio的Logcat中你会看到以┬───、│、├─、└─等字符构成的树形图清晰地描绘出引用链。这份报告会明确指出泄漏的对象例如MainActivity实例。持有的引用链从GC Root如一个静态的Helper类开始到最终持有MainActivity的引用路径。例如┬─── │ GC Root: Static field com.example.MyApp.sHelper │ ├─ com.example.MyHelper instance │ Leaking: UNKNOWN │ ↓ MyHelper.context │ ~~~~~~~~ └─ com.example.MainActivity instance可能的原因LeakCanary甚至会根据常见的泄漏模式给出可能的原因提示比如“Activity的Context被一个静态变量持有”。通过这四步LeakCanary完成了从自动监控、精准判断、深度分析到清晰报告的全流程将一个原本需要专业工具和大量经验的任务变成了一个自动化、可视化的日常开发环节。3. 从集成到配置让LeakCanary在你的项目中落地理解了原理接下来就是动手把它集成到你的项目里。LeakCanary的集成非常简单但一些细节配置能让你用得更顺手。这里我们以目前主流的LeakCanary 2.x版本为例。3.1 基础依赖集成在你的App模块的build.gradle或build.gradle.kts文件中添加LeakCanary的依赖。请注意通常只应该在debug构建变体中添加因为内存泄漏检测和分析会消耗额外资源不应发布到生产环境。dependencies { // 使用 debugImplementation确保只在调试版本引入 debugImplementation com.squareup.leakcanary:leakcanary-android:2.12 }是的就这么一行。添加依赖后同步项目LeakCanary就已经在后台开始工作了。下次你运行debug版本的App时它就会自动初始化并开始监控。你可能会在Logcat里看到类似LeakCanary is watching for activities的日志。3.2 自定义配置与应用场景默认配置对大多数项目来说已经足够好用但LeakCanary提供了丰富的配置选项来适应不同需求。配置需要在Application类中进行。如果你还没有自定义的Application类需要创建一个并记得在AndroidManifest.xml中声明。// MyApp.kt import android.app.Application import leakcanary.AppWatcher import leakcanary.LeakCanary class MyApp : Application() { override fun onCreate() { super.onCreate() // 方式一完全使用默认配置最简单的初始化 // LeakCanary会自动调用 AppWatcher.manualInstall(this) // 方式二进行自定义配置推荐 val config LeakCanary.config.copy( // 1. 配置需要监视的对象类型 watchActivities true, // 默认true监视Activity watchFragments true, // 默认true监视Fragment watchFragmentViews true, // 默认true监视Fragment的View watchViewModelCleared true, // 默认true监视ViewModel cleared后 // 2. 配置延迟时间单位毫秒 watchDurationMillis 5000, // 默认5000ms即5秒 // 3. 配置触发堆转储的条件 dumpHeap true, // 默认true检测到泄漏时是否dump堆 dumpHeapWhenDebugging false, // 调试时是否dump默认false避免干扰 retainedVisibleThreshold 1, // 当泄漏对象数达到多少时触发dump默认1 // 4. 配置通知行为 onHeapAnalyzedListener DefaultOnHeapAnalyzedListener.create(), // 5. 配置元数据方便在CI/CD中定位问题 metadataExtractor CustomMetadataExtractor() ) LeakCanary.config config } }关键配置解析watchDurationMillis观察期这是前面原理中提到的“等待GC”的时间。对于大型应用或低端设备如果5秒后GC仍未完全生效可能会误报。你可以适当延长这个时间比如10000毫秒。反之如果你想更快得到结果可以缩短但会增加误报风险。dumpHeapWhenDebugging默认是false。这是因为当你在Android Studio中调试并打了断点时JVM会挂起所有线程此时dump堆可能会导致进程挂起或分析失败。保持默认即可。retainedVisibleThreshold这个配置非常有用。假设你的App有一个全局的缓存池里面可能会暂时持有一些本该销毁的Activity引用虽然设计上可能有问题但短期内无法修改。你可以将这个值设为3或5这样只有当一个Activity实例泄漏数量达到阈值时LeakCanary才会发出通知和dump堆避免被已知的、暂时无法解决的“预期内泄漏”刷屏。3.3 扩展监视范围自定义监视对象LeakCanary不仅能监视Android框架组件还能监视任何你关心的对象。比如你有一个自定义的UserSession单例或者一个全局的Bitmap缓存池你想知道它们是否在不再需要时被正确释放。你可以使用AppWatcher.objectWatcher.watch()方法来手动添加监视class MyManager { private val heavyResource HeavyResource() fun doSomething() { // ... 使用 heavyResource } fun cleanup() { // 业务逻辑上的清理 heavyResource.release() // 告诉LeakCanary这个对象现在应该被回收了 AppWatcher.objectWatcher.watch( watchedObject heavyResource, description MyManager.heavyResource ) } }在cleanup()方法被调用后LeakCanary就会开始监视heavyResource对象。如果在接下来的观察期默认5秒后它仍然存活LeakCanary就会报告它泄漏了。这个功能在检测非生命周期组件的资源管理时非常强大。3.4 在持续集成CI中运行LeakCanary在本地开发时LeakCanary的通知非常方便。但在自动化测试中我们需要它能以“静默”的方式运行并在发现泄漏时让测试失败从而阻止有内存问题的代码被合并。LeakCanary提供了LeakCanaryRunningTestRuleJUnit4或LeakCanaryTestRule更通用来支持这一点。首先在测试依赖中添加androidTestImplementation com.squareup.leakcanary:leakcanary-android-instrumentation:2.12然后在你的UI测试类中例如Espresso测试import leakcanary.FailTestOnLeakRunListener import leakcanary.LeakCanaryTestRule import org.junit.Rule import org.junit.Test class MyUiTest { // 添加这个Rule它会在每个测试方法执行后检查泄漏 get:Rule val leakCanaryRule LeakCanaryTestRule() Test fun myScreenTest() { // 你的Espresso测试代码... onView(withId(R.id.button)).perform(click()) // 测试结束时LeakCanaryRule会自动检查是否有泄漏发生 // 如果有测试会失败并输出泄漏信息到日志 } }这样每当你的CI服务器运行UI测试时任何在测试过程中引入的内存泄漏都会导致测试失败并将详细的泄漏堆栈输出到日志中方便排查。这是将内存安全左移融入开发流程的关键一步。4. 解读LeakCanary报告从报警信息到问题根因收到LeakCanary的报警通知只是第一步。更重要的是你要能看懂它给你的报告并快速定位到代码中的问题所在。一份典型的LeakCanary报告包含丰富的信息我们需要学会提取关键点。4.1 报告结构深度解析点击通知栏的泄漏通知你会进入LeakCanary的详情页面一个独立的Activity。或者你也可以直接在Logcat中查看输出。我们以一个经典的Activity被静态变量持有的泄漏为例来拆解报告。Logcat输出示例 HEAP ANALYSIS RESULT 1 APPLICATION LEAKS References underlined with ~~~ are likely causes. Learn more at https://squ.re/leaks. 1个泄漏的MainActivity实例保留在堆中。 关键路径GC Root → 静态变量 com.example.MyApp.sInstance → 字段 com.example.MyApp.context → 实例 com.example.MainActivity ┬─── │ GC Root: Static field com.example.MyApp.sInstance │ ├─ com.example.MyApp instance │ Leaking: UNKNOWN │ ↓ MyApp.context │ ~~~~~~~ └─ com.example.MainActivity instance Leaking: YES (ObjectWatcher reported this object as retained) ↓ MainActivity.mDestroyed ~~~~~~~~~~~ 0 LIBRARY LEAKS页面展示示例更直观会以图形化的方式展示上面的引用链并高亮可疑的引用。报告关键元素解读泄漏分类APPLICATION LEAKS vs LIBRARY LEAKSAPPLICATION LEAKS应用泄漏这是你的应用程序代码导致的内存泄漏。这是你需要重点关注并修复的。报告开头会告诉你发现了多少个此类泄漏。LIBRARY LEAKS库泄漏这是由你使用的第三方库包括Android系统库引起的泄漏。有时你无法直接修复它但知道它的存在很重要。LeakCanary会尽力识别并归类它们。GC Root垃圾回收根这是泄漏引用链的起点。上例中是com.example.MyApp.sInstance这个静态变量。找到它就找到了问题的源头。常见的GC Root还有Thread实例、ThreadLocal等。引用链Reference Chain用树状图┬───,├─,└─清晰地展示了从GC Root到泄漏对象MainActivity的完整引用路径。每一行代表一个对象和它持有的字段。可疑引用标记~~~LeakCanary会根据经验用波浪线~~~下划线标出它认为“最可能”导致泄漏的引用字段。在上面的例子中MyApp.context字段被标记了。这通常是你应该首先检查的地方。注意它标记的是引用而不是对象本身。泄漏状态Leaking: YES/NO/UNKNOWN明确告诉你该对象是否被判定为泄漏。ObjectWatcher reported this object as retained明确说明是LeakCanary监视并确认的泄漏。4.2 常见泄漏模式与实战修复根据报告我们可以快速对应到几种最常见的泄漏模式模式一静态变量持有Context/Activity报告特征GC Root是静态变量引用链最终指向一个Activity。代码示例class MyHelper { companion object { var context: Context? null // 错误静态变量持有Context } } // 在Activity中MyHelper.context this修复方案首选避免用静态变量持有Context或View。如果需要Context通过参数传递或者使用ApplicationContext如果适用。如果必须持有确保在适当的时候如onDestroy()将引用置为null。使用弱引用如果确实需要全局访问考虑使用WeakReferenceContext。class MyHelper { companion object { private var weakContext: WeakReferenceContext? null fun setContext(ctx: Context) { weakContext WeakReference(ctx.applicationContext) // 使用ApplicationContext } } }模式二匿名内部类/非静态内部类持有外部类引用报告特征泄漏对象是一个Activity或Fragment引用链中出现了Handler、Runnable、Thread或自定义Listener的实例并且这些实例被长生命周期对象持有。代码示例class MainActivity : AppCompatActivity() { private val handler object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { // 这里隐式持有了外部类MainActivity的引用 updateUI() } } // 或者 private val runnable Runnable { // 同样隐式持有MainActivity引用 doSomething() } }修复方案将内部类声明为static在Kotlin中是嵌套类class Nested这样它就不会持有外部类的引用。如果内部类必须访问外部类的成员那么持有外部类的引用应使用弱引用。class MainActivity : AppCompatActivity() { // 静态内部类不持有Activity引用 private class MyHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) { private val activityRef: WeakReferenceMainActivity WeakReference(activity) override fun handleMessage(msg: Message) { activityRef.get()?.updateUI() // 安全地获取引用 } } private val handler MyHandler(this) }对于Handler和Runnable务必在onDestroy()中调用handler.removeCallbacksAndMessages(null)来移除所有待处理的消息。模式三单例模式管理不当报告特征GC Root是一个单例实例该单例的某个成员变量持有了Activity等短生命周期对象。代码示例一个管理监听器的单例注册了Activity作为监听器后忘记反注册。修复方案在单例中使用WeakReference集合来保存监听器或者建立严格的注册/反注册机制并确保在Activity销毁时onDestroy调用反注册方法。模式四资源未关闭如Cursor、Stream、BroadcastReceiver虽然LeakCanary主要监控对象但某些资源未关闭会间接导致对象泄漏如Activity中注册的BroadcastReceiver未取消注册。修复方案在onDestroy()或onPause()中确保关闭所有打开的资源和取消所有注册。4.3 处理“预期内”的泄漏与误报不是所有LeakCanary报告的问题都需要立刻、马上修复。你需要学会区分。已知的第三方库泄漏LIBRARY LEAKS比如某些旧版本的Support库或特定厂商ROM的Bug。你可以记录下它并关注该库的更新。LeakCanary提供了忽略库泄漏的配置选项LeakCanary.config config.copy(referenceMatchers AndroidReferenceMatchers.appDefaults)已经内置了很多忽略规则。“暂时性”持有有些对象可能被全局缓存或线程池短暂持有稍后会被释放。如果确认这是设计如此且生命周期可控你可以通过配置retainedVisibleThreshold提高触发阈值或者使用AppWatcher.objectWatcher.expectWeaklyReachable(watchedObject, description)在代码中标记某个对象“预期可达”让LeakCanary忽略对它的检查。系统Bug或无法修复的泄漏极少数情况下可能是Android系统本身的Bug。如果经过排查确认不是应用代码问题且无法规避这至少是一个已知风险点。重要原则对于APPLICATION LEAKS尤其是Activity和Fragment的泄漏应该秉持“零容忍”态度。因为它们是内存消耗大户且直接关联用户体验。每一个这样的泄漏都意味着用户每打开一次这个界面就会有一份该界面的内存被永久占用直到App进程被杀死。这在低端设备上会迅速引发OOM。5. 超越基础LeakCanary的高级用法与最佳实践当你熟练使用LeakCanary处理日常泄漏后可以进一步探索它的高级功能并将其融入团队开发流程构建更坚固的内存防线。5.1 自定义泄漏分析与堆转储上传LeakCanary的分析结果HeapAnalysis对象包含了所有细节。你可以通过自定义OnHeapAnalyzedListener来拦截这个结果做更多事情比如将泄漏报告上传到你的后端服务器进行聚合分析或者在CI环境中让构建失败。class CustomLeakUploader : OnHeapAnalyzedListener { override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) { // 1. 将分析结果转换为JSON等可序列化格式 val json LeakCanary.config.heapDumpParser.analyzeHeap(heapAnalysis) // 或者直接使用 heapAnalysis.toString() // 2. 上传到你的服务器注意在后台线程进行 uploadToServer(json) // 3. 可选在CI环境中如果发现应用泄漏可以抛出异常使测试失败 if (heapAnalysis is HeapAnalysisSuccess) { val applicationLeaks heapAnalysis.allLeaks.filter { it is ApplicationLeak } if (applicationLeaks.isNotEmpty()) { throw AssertionError(发现内存泄漏: ${applicationLeaks.joinToString(\n)}) } } // 4. 最后别忘了调用默认的监听器来显示通知如果你还需要的话 DefaultOnHeapAnalyzedListener.create().onHeapAnalyzed(heapAnalysis) } private fun uploadToServer(analysisData: String) { // 使用OkHttp, Retrofit等工具上传 } } // 在Application中配置 LeakCanary.config LeakCanary.config.copy( onHeapAnalyzedListener CustomLeakUploader() )这对于团队协作和追踪历史泄漏问题非常有帮助。你可以建立一个仪表盘查看哪个模块、哪个开发者引入的泄漏最多以及泄漏的修复趋势。5.2 与其他性能工具联动LeakCanary解决的是内存“正确性”问题不该有的对象有没有。而要全面优化应用性能你还需要关注内存“效率”问题该有的对象用了多少内存。这时需要结合其他工具Android Studio Profiler (Memory Profiler)这是官方的一站式性能分析工具。当LeakCanary帮你找到并修复了明显的泄漏后你应该定期使用Profiler来查看实时内存分配了解你的应用在用户操作下的真实内存使用曲线。捕获堆转储进行更深入分析Profiler的堆转储查看器功能更强大可以按类、按包查看实例数、浅堆大小、深堆大小帮助你发现那些虽然没泄漏但数量过多或体积过大的对象比如Bitmap缓存过大。跟踪内存分配记录一段时间内所有对象的分配调用栈找到内存分配的“热点”。Matrix等APM工具在线上环境中你无法使用LeakCanary。但可以通过接入腾讯Matrix等应用性能监控平台它们通常具备线上OOM监控和堆栈聚合功能。虽然不能像LeakCanary那样给出精确的引用链但能告诉你OOM发生时的主要线程堆栈和内存概况为线上问题定位提供关键线索。将LeakCanary在开发测试阶段发现的问题模式与线上APM报告的问题进行对照能更全面地把握应用的内存健康度。5.3 建立团队内存安全规范工具再好也需要人来正确使用。将LeakCanary融入团队流程能最大化其价值强制集成在项目模板或基础模块中默认集成LeakCanary的debugImplementation依赖。让每个新项目从一开始就具备内存检测能力。CI/CD门禁如前所述在UI测试套件中集成LeakCanaryTestRule。确保任何提交的代码如果引入了新的Activity/Fragment泄漏自动化测试会失败阻止合并。可以将此作为MR/PR合并的一个必要条件。代码审查清单在团队的代码审查清单中加入“内存泄漏检查”一项。审查者可以要求作者提供LeakCanary在相关页面操作后的“清白”截图无泄漏通知或者询问对单例、静态变量、监听器注册等高风险代码的处理方式。定期“内存扫除”在每个版本开发周期的末期安排一个专门的时间段让开发者运行一遍核心流程检查LeakCanary是否有新的报警。修复已知泄漏并将暂时无法解决的“预期内泄漏”记录在案。新人培训在新成员入职培训中专门讲解如何使用LeakCanary和解读报告。分享团队历史上遇到过的经典泄漏案例及其修复方案这是最好的实践教材。5.4 疑难杂症与性能考量LeakCanary本身会影响性能吗会但影响可控。在debug构建中额外的对象监视、堆转储仅在检测到泄漏时发生和分析在独立进程会消耗CPU和内存并可能引起短暂的界面卡顿尤其是在dump堆时。这正是为什么它只应该用于debug版本。对于性能极度敏感的场景你可以在性能测试时临时关闭LeakCanary通过LeakCanary.config LeakCanary.config.copy(dumpHeap false)。为什么有些泄漏复现不了内存泄漏有时具有偶发性与GC时机、线程调度、用户操作顺序密切相关。LeakCanary的检测也存在概率尽管很高。如果怀疑有泄漏但LeakCanary没报可以尝试多次重复可疑操作。在触发操作后手动点击Android Studio Profiler的“强制垃圾回收”按钮然后观察内存是否回落。使用AppWatcher.objectWatcher.watch()手动监视可疑对象。HPROF文件太大分析失败对于大型应用堆转储文件可能超过1GB。分析这样的文件需要大量内存和时间甚至可能失败。可以考虑增加分析进程的堆内存通过LeakCanary.config配置或者专注于在内存使用较低的场景下复现和捕获泄漏。从我个人的经验来看LeakCanary的价值不仅仅在于它帮你找到了多少个Bug更在于它潜移默化地改变了你编写代码的思维方式。你会开始本能地警惕静态变量、审视内部类、关注生命周期对称性注册/反注册、绑定/解绑。这种对内存的“洁癖”是成为一名高级Android开发者的重要标志。把它用起来坚持下去你会发现那些令人头疼的OOM崩溃会越来越少应用的流畅度和稳定性则会显著提升。