使用方法
目前最新版本是2.7,使用起来比较简单,只需要在gradle里面加入一句话就可以了.而且debugImplementation只在debug模式下有效,所以不用担心用户在正式环境下也会出现LeakCanary收集。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}
在项目中加入LeakCanary之后就可以开始检测项目的内存泄露了,把项目运行起来之后, 开始随便点自己的项目,下面以一个Demo项目为例,来聊一下LeakCanary记录内存泄露的过程以及我如何解决内存泄露的。
项目运行起来之后,在控制台可以看到LeakCanary的打印信息:
2021-05-26 10:47:44.894 3704-3751/com.frj.memerytest D/LeakCanary: LeakCanary is running and ready to detect memory leaks.
2021-05-26 10:47:46.443 3704-3742/com.frj.memerytest D/LeakCanary: Setting up flushing for Thread[LeakCanary-Heap-Dump,5,main]
2021-05-26 10:44:00.461 17731-17731/com.frj.memerytest D/LeakCanary: Watching instance of android.arch.lifecycle.ReportFragment (android.arch.lifecycle.ReportFragment received Fragment#onDestroy() callback) with key f1a14db2-ae06-401c-9e3b-32e5484201bb
2021-05-26 10:44:00.462 17731-17731/com.frj.memerytest D/LeakCanary: Watching instance of com.mingjian.memerytest.MemoryOomActivity (com.mingjian.memerytest.MemoryOomActivity received Activity#onDestroy() callback) with key db22bbc6-cfbd-4259-86fd-67f4d84dda3e
这说明LeakCanary正在不断的检测项目中是否有剩余对象。那么LeakCanary是如何工作的呢?LeakCanary的基础是一个叫做ObjectWatcher Android的library。它hook了Android的生命周期,当activity和fragment 被销毁并且应该被垃圾回收时候自动检测。这些被销毁的对象被传递给ObjectWatcher, ObjectWatcher持有这些被销毁对象的弱引用(weak references)。如果弱引用在等待5秒钟并运行垃圾收集器后仍未被清除,那么被观察的对象就被认为是保留的(retained,在生命周期结束后仍然保留),并存在潜在的泄漏。LeakCanary会在Logcat中输出这些日志。
2021-05-26 10:44:04.279 17731-17731/com.frj.memerytest D/LeakCanary: Watching instance of android.arch.lifecycle.ReportFragment (android.arch.lifecycle.ReportFragment received Fragment#onDestroy() callback) with key ec552863-1e83-4435-a701-081e39bf87ae
2021-05-26 10:44:04.280 17731-17731/com.frj.memerytest D/LeakCanary: Watching instance of com.mingjian.memerytest.MemoryShake (com.mingjian.memerytest.MemoryShake received Activity#onDestroy() callback) with key 1f1259f2-a8ba-417c-8493-e0fa1c27fd3f
2021-05-26 10:44:09.394 17731-17793/com.frj.memerytest D/LeakCanary: Found 2 objects retained, not dumping heap yet (app is visible & < 5 threshold)
我一直来回进入内测泄漏和内存抖动的页面,一会就有通知提示了,点击这个通知,可以在手机上查看信息
image.png
image.png
此时也可以在logcat上查看信息
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
227457 bytes retained by leaking objects
Displaying only 1 leak trace out of 3 with the same signature
Signature: ad8152279f1def7a0b3dedc96584fda8974225
┬───
│ GC Root: System class
│
├─ android.os.Looper class
│ Leaking: NO (Thread↓ is not leaking and a class is never leaking)
│ ↓ static Looper.sMainLooper
├─ android.os.Looper instance
│ Leaking: NO (Thread↓ is not leaking)
│ ↓ Looper.mThread
├─ java.lang.Thread instance
│ Leaking: NO (the main thread always runs)
│ Thread name: 'main'
│ ↓ Thread.threadLocals
│ ~~~~~~~~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap instance
│ Leaking: UNKNOWN
│ Retaining 1.3 kB in 44 objects
│ ↓ ThreadLocal$ThreadLocalMap.table
│ ~~~~~
├─ java.lang.ThreadLocal$ThreadLocalMap$Entry[] array
│ Leaking: UNKNOWN
│ Retaining 1.3 kB in 43 objects
│ ↓ ThreadLocal$ThreadLocalMap$Entry[].[7]
│ ~~~
├─ java.lang.ThreadLocal$ThreadLocalMap$Entry instance
│ Leaking: UNKNOWN
│ Retaining 28 B in 1 objects
│ ↓ ThreadLocal$ThreadLocalMap$Entry.value
│ ~~~~~
├─ android.animation.AnimationHandler instance
│ Leaking: UNKNOWN
│ Retaining 457.9 kB in 6954 objects
│ ↓ AnimationHandler.mAnimationCallbacks
│ ~~~~~~~~~~~~~~~~~~~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ Retaining 457.8 kB in 6950 objects
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ Retaining 457.8 kB in 6949 objects
│ ↓ Object[].[3]
│ ~~~
├─ android.animation.ValueAnimator instance
│ Leaking: UNKNOWN
│ Retaining 76.3 kB in 1158 objects
│ ↓ ValueAnimator.mUpdateListeners
│ ~~~~~~~~~~~~~~~~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ Retaining 75.9 kB in 1145 objects
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ Retaining 75.9 kB in 1144 objects
│ ↓ Object[].[0]
│ ~~~
├─ view.IOSStyleLoadingView1$1 instance
│ Leaking: UNKNOWN
│ Retaining 75.8 kB in 1143 objects
│ Anonymous class implementing android.animation.ValueAnimator$AnimatorUpdateListener
│ ↓ IOSStyleLoadingView1$1.this$0
│ ~~~~~~
├─ view.IOSStyleLoadingView1 instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 75.8 kB in 1142 objects
│ View is part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 1
│ context instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│ mContext instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│ ↓ IOSStyleLoadingView1.context
╰→ com.mingjian.memerytest.MemoryShake instance
Leaking: YES (ObjectWatcher was watching this because com.mingjian.memerytest.MemoryShake received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 7.5 kB in 119 objects
key = 6723e90d-1567-4e34-99d7-714f96d0aa42
watchDurationMillis = 22266
retainedDurationMillis = 17261
mApplication instance of android.app.Application
mBase instance of android.app.ContextImpl
====================================
0 LIBRARY LEAKS
路径中的每一个节点都对应着一个java对象。熟悉java内存回收机制的同学都应该知道”可达性分析算法“,LeakCanary就是用可达性分析算法,从GC ROOTS向下搜索,一直去找引用链,如果某一个对象跟GC Roots没有任何引用链相连时,就证明对象是”不可达“的,可以被回收。
我们从上往下看:
GC Root: System class
在泄漏路径的顶部是GC Root。GC Root是一些总是可达的特殊对象。
接着是:
─ android.os.Looper class
│Leaking: NO (Thread↓ is not leaking and a class is never leaking)
│ ↓ static Looper.sMainLooper
这里先看一下Leaking的状态(YES、NO、UNKNOWN),NO表示没泄露。那我们还得接着向下看。一直到view.IOSStyleloadingView1 instance 才出现Yes
├─ view.IOSStyleLoadingView1 instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 75.8 kB in 1142 objects
│ View is part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 1
│ context instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│ mContext instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│ ↓ IOSStyleLoadingView1.context
这说明这里出现了内存泄漏,这里直接指出来是我们自写的View出现了内存泄漏
一般推断内存泄露是从最后一个没有泄漏的节点(Leaking: NO )到第一个泄漏的节点(Leaking: YES)之间的引用。
├─ view.IOSStyleLoadingView1$1 instance
│ Leaking: UNKNOWN
│ Retaining 75.8 kB in 1143 objects
│ Anonymous class implementing android.animation.ValueAnimator$AnimatorUpdateListener
│ ↓ IOSStyleLoadingView1$1.this$0
│ ~~~~~~
├─ view.IOSStyleLoadingView1 instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 75.8 kB in 1142 objects
│ View is part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 1
│ context instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│ mContext instance of com.mingjian.memerytest.MemoryShake with mDestroyed = true
│ ↓ IOSStyleLoadingView1.context
╰→ com.mingjian.memerytest.MemoryShake
可以看到liaking状态是YES之前是AnimatorUpdateListener,那就说明是此listener没有做释放,非常精准的定位问题了
对于每个被保留的对象,LeakCanary会找出阻止该保留对象被回收的引用链:泄漏路径。泄露路径就是从GC ROOTS到保留对象的最短的强引用路径的别名。确定泄漏路径以后,LeakCanary使用它对Android框架的了解来找出在泄漏路径上是谁泄漏了。
leak状态说明
NO: 没有内存泄漏
UNKNOWN: 表示这里可能出现了内存泄露,这些引用你需要花时间来调查一下,看看是哪里出了问题。
YES: 表示此处有内存泄漏
网友评论