LeakCanary 是一个开源的内存泄漏检测库,专门用于 Android 开发。它的主要功能是在应用程序运行时,自动检测并报告那些本应被销毁但仍然被引用的对象(比如一个已经关闭的Activity),从而帮助开发者找到并修复内存泄漏问题。
它是如何工作的?
LeakCanary 的工作原理可以简单概括为以下几步:
- 监控:它会监控应用程序中一些“生命周期结束”的对象,比如当一个
Activity调用了onDestroy()方法,或者一个Fragment的onDestroyView()被调用时。 - 观察:在这些对象生命周期结束时,LeakCanary 会将它们放入一个名为
ObjectWatcher的“观察者”中,并启动一个短时间的延时。 - 判断:在延时结束后,它会主动触发一次垃圾回收(GC),然后检查这些被观察的对象是否已经被回收。
- 报告:如果某个对象在 GC 后仍然存在,那么 LeakCanary 就认为它发生了内存泄漏。它会立刻生成一个堆转储文件(hprof 文件)。
- 分析:LeakCanary 会在后台进程中分析这个堆转储文件,找到从 GC Root 到泄漏对象的完整引用链。这个引用链就是你看到的那份报告,它清晰地展示了“谁”持有了这个本应被销毁的对象。
为什么它很重要?
在 Android 开发中,内存泄漏是一个常见但又难以发现的问题。长时间的内存泄漏会导致以下后果:
- 性能下降:泄漏的内存越来越多,可用内存变少,会导致应用程序运行变慢,甚至出现卡顿。
- 内存溢出(OOM):如果泄漏的内存持续累积,最终会耗尽应用进程的可用内存,导致
OutOfMemoryError,应用就会崩溃。 - 用户体验差:卡顿和崩溃都会严重影响用户体验。
LeakCanary 的价值就在于,它能以一种非常直观和自动化的方式,帮助开发者在测试阶段就发现并定位这些问题,而不是等到应用发布后由用户来发现。你看到的这份详细的引用链报告,就是它最强大的功能,它直接指出了问题的根源,让修复工作变得简单高效。
实战
┬───
│ GC Root: Thread object
│
├─ android.net.ConnectivityThread instance
│ Leaking: NO (PathClassLoader↓ is not leaking)
│ Thread name: 'ConnectivityThread'
│ ↓ Thread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│ Leaking: NO (HttpExecutor↓ is not leaking and A ClassLoader is never leaking)
│ ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (HttpExecutor↓ is not leaking)
│ ↓ Object[23397]
├─ com.baidu.android.imsdk.utils.HttpExecutor class
│ Leaking: NO (a class is never leaking)
│ ↓ static HttpExecutor.mInstance
│ ~~~~~~~~~
├─ com.baidu.android.imsdk.utils.HttpExecutor instance
│ Leaking: UNKNOWN
│ Retaining 12 B in 1 objects
│ ↓ HttpExecutor.okHttpClient
│ ~~~~~~~~~~~~
├─ okhttp3.OkHttpClient instance
│ Leaking: UNKNOWN
│ Retaining 235 B in 7 objects
│ ↓ OkHttpClient.dispatcher
│ ~~~~~~~~~~
├─ okhttp3.Dispatcher instance
│ Leaking: UNKNOWN
│ Retaining 5.3 kB in 109 objects
│ ↓ Dispatcher.runningAsyncCalls
│ ~~~~~~~~~~~~~~~~~
├─ java.util.ArrayDeque instance
│ Leaking: UNKNOWN
│ Retaining 84 B in 2 objects
│ ↓ ArrayDeque.elements
│ ~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ Retaining 64 B in 1 objects
│ ↓ Object[5]
│ ~~~
├─ okhttp3.RealCall$AsyncCall instance
│ Leaking: UNKNOWN
│ Retaining 7.5 MB in 22379 objects
│ ↓ RealCall$AsyncCall.responseCallback
│ ~~~~~~~~~~~~~~~~
├─ com.baidu.searchbox.http.request.RequestCall$6 instance
│ Leaking: UNKNOWN
│ Retaining 7.5 MB in 22378 objects
│ Anonymous class implementing okhttp3.Callback
│ ↓ RequestCall$6.val$callback
│ ~~~~~~~~~~~~
├─ com.baidu.searchbox.video.feedflow.ad.position.activerecommend.NadFlowActiveRecommendManager$request$responseCb$1 instance
│ Leaking: UNKNOWN
│ Retaining 7.5 MB in 22376 objects
│ Anonymous subclass of com.baidu.searchbox.http.callback.ResponseCallback
│ ↓ NadFlowActiveRecommendManager$request$responseCb$1.$listener
│ ~~~~~~~~~
├─ com.baidu.searchbox.video.feedflow.ad.position.AdPositionPlugin$onAttachToManager$2$1$2 instance
│ Leaking: UNKNOWN
│ Retaining 7.5 MB in 22327 objects
│ Anonymous class implementing com.baidu.searchbox.video.feedflow.ad.position.INadRecommendRequestListener
│ ↓ AdPositionPlugin$onAttachToManager$2$1$2.this$0
│ ~~~~~~
├─ com.baidu.searchbox.video.feedflow.ad.position.AdPositionPlugin instance
│ Leaking: UNKNOWN
│ Retaining 7.5 MB in 22325 objects
│ context instance of com.baidu.searchbox.video.feedflow.tab.VideoTabActivity with mDestroyed = true
│ ↓ AbsPlugin.context
│ ~~~~~~~
╰→ com.baidu.searchbox.video.feedflow.tab.VideoTabActivity instance
Leaking: YES (ObjectWatcher was watching this because com.baidu.searchbox.video.feedflow.tab.VideoTabActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 2.0 MB in 903 objects
key = 9e088d53-3a5b-4a72-abf3-a47757d1190a
watchDurationMillis = 5329
retainedDurationMillis = 328
mApplication instance of com.baidu.searchbox.SearchboxApplication
mBase instance of android.app.ContextImpl
手机型号: SM-G9730
ROM版本: 10
cuid: B49DAF6912A2BE94226FF0CCCDEF2C81|0
分支: master
Commit ID: 28b2e3995ecd2436c6b21181e1d8e58bab5c3ace
APP Version Code: 506200192
hprof下载地址: http://creator.soarx.cc/hprof/20250722/e57f5731-a916-4313-8d2b-f2d51e986777_com_baidu_searchbox.gz
这部分日志是 LeakCanary 内存泄漏检测工具生成的报告,它显示了一个内存泄漏的引用链。
报告展示了一个从 GC Root(垃圾回收根节点)开始的对象引用链,最终指向一个泄漏的对象。这个引用链描述了为什么某个对象无法被垃圾回收器回收,导致内存泄漏。以下是报告的逐步分析:
报告解读
我们从下往上、从后往前看,这是理解泄露路径的最佳方式。
╰→ com.baidu.searchbox.video.feedflow.tab.VideoTabActivity instance: 这是报告的起点,也是被泄漏的对象。LeakCanary 明确指出Leaking: YES,因为它在onDestroy()后依然存在,且被一个ObjectWatcher持续观察。它占用了大约 2.0 MB 的内存,这在移动应用中是个不小的数字。├─ com.baidu.searchbox.video.feedflow.ad.position.AdPositionPlugin instance: 这是一个AdPositionPlugin的实例。它通过其context字段,持有了对上面VideoTabActivity的引用。报告中明确写道context instance of ...VideoTabActivity with mDestroyed = true,这表明AdPositionPlugin正在持有本应被销毁的Activity的上下文。├─ com.baidu.searchbox.video.feedflow.ad.position.AdPositionPlugin: 这是一个匿名内部类,它通过onAttachToManager212 instancethis$0字段隐式地持有了对其外部类AdPositionPlugin的引用。**├─ com.baidu.searchbox.video.feedflow.ad.position.NadFlowActiveRecommendManagerrequestresponseCb1 instance**</code>: 另一个匿名内部类,通过`listener`字段持有了上一个匿名类的引用。**├─ com.baidu.searchbox.http.request.RequestCall6 instance**</code>: 这是一个实现了`okhttp3.Callback`接口的匿名内部类,通过`valcallback`字段持有了上一个匿名类的引用。├─ okhttp3.RealCall$AsyncCall instance: 这是 OkHttp 网络请求库中的一个异步调用实例。它通过responseCallback字段持有了上面那个Callback实例的引用。├─ java.lang.Object[] array** & **├─ java.util.ArrayDeque instance: 这是 OkHttp 的Dispatcher(调度器)内部用来管理正在运行的异步请求的队列。这个Object[]数组中包含了那个AsyncCall实例。├─ okhttp3.Dispatcher instance: OkHttp 的调度器,它维护着待处理和正在处理的网络请求队列,其中包含了导致泄漏的那个AsyncCall。├─ okhttp3.OkHttpClient instance: OkHttp 的主客户端对象,它包含了Dispatcher实例。├─ com.baidu.android.imsdk.utils.HttpExecutor instance: 这是一个 HttpExecutor 单例。它通过okHttpClient字段持有了 OkHttpClient 实例。├─ com.baidu.android.imsdk.utils.HttpExecutor class** &├─ java.lang.Object[] array**** &├─ dalvik.system.PathClassLoader instance**** & **├─ android.net.ConnectivityThread instance: 这些是泄露链的起始点,即 GC Root。这是一个Thread(ConnectivityThread)对象。它通过一系列类加载器和静态变量引用,最终指向了HttpExecutor的单例。
总结
整个泄漏链的根源在于:
一个名为ConnectivityThread的线程,通过一系列静态引用和单例对象(HttpExecutor),持有一个 OkHttp 客户端实例。这个 OkHttp 客户端正在执行一个异步网络请求(AsyncCall)。这个请求的回调链最终指向了AdPositionPlugin,而这个AdPositionPlugin又错误地持有了已销毁的VideoTabActivity实例的上下文。
所以,只要这个网络请求没有完成,它的回调对象链就会一直存在,导致Activity无法被回收,从而发生内存泄漏。其实是不影响最终结果的,最终请求会被释放,内存也就释放了。但是没有在正确的位置上释放,就是内存泄漏,需要修复。
更新: 2025-08-13 22:50:43
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/ntm96humslvyacwb
相关笔记