1. LeakCanary检测泄漏原理
1.1 Java引用分类
- 强引用:平时常用的引用类型,JVM发生OOM也不会回收这部分引用。
- 软引用(SoftReference):发生OOM前会回收这部分引用,如果想使用缓存,可以使用
LruCache,而不是SoftReference - 弱引用(WeakReference):发生GC就会回收。
- 虚引用(PhantomReference):get方法返回null,不能获取值。
1.2 WeakReference和ReferenceQueue
class P
fun main() {
val referenceQueue = ReferenceQueue<P>()
val weak = WeakReference(P(), referenceQueue)
println(weak)
System.gc()
Thread.sleep(2000)
println(weak.get())
println(referenceQueue.poll())
}
//输出:
java.lang.ref.WeakReference@2f0e140b
null
java.lang.ref.WeakReference@2f0e140b
当WeakReference引用的对象没有其他对象引用,在GC时,会回收该对象,如果创建WeakReference时传入了ReferenceQueue,会在对象回收后把WeakReference加入到ReferenceQueue队列中。
1.3 LeakCanary的检测原理
在Activity执行onDestroy()方法后,把activity对象放进一个带有ReferenceQueue的WeakReference对象中。过段时间去检测ReferenceQueue队列里是否包含刚刚创建的WeakReference对象,如果没有,则表明activity对象还没被回收,则主动触发一次GC,再次检测。如果还没被回收,则表明发生了泄漏。开始使用Debug.dumpHprofData()生成hprof文件,分析hprof文件,得出引用链。
2. LeakCanary源码分析
源码版本2.7
com.squareup.leakcanary:leakcanary-android:2.7
2.1 初始化
使用ContentProvider,自动初始化。
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
}
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
checkMainThread()
//默认5s后去检测
this.retainedDelayMillis = retainedDelayMillis
//初始化一些配置,InternalLeakCanary类invoke方法,配置泄漏Listener,GC触发器,Dump类。
LeakCanaryDelegate.loadLeakCanary(application)
//监测类别,Activity,Fragment,View,Service
watchersToInstall.forEach {
it.install()
}
}
监测类别
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
使用ActivityLifecycleCallbacks来监听onActivityDestroyed的方法回调。
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
ObjectWatcher的创建,执行检测的对象。
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
//把runnable post 5s后执行
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)
2.2 添加WeakReference检测对象是否回收
用activity举例,会在onActivityDestroyed后,调用expectWeaklyReachable方法,创建KeyedWeakReference对象,放进watchedObjects的map中。延迟5s后,先根据 ReferenceQueue中的对象移除map中的对应KeyedWeakReference,如果移除(ReferenceQueue有对应的KeyedWeakReference),表明activity被回收,如果未能移除,map还包含该KeyedWeakReference,则表明可能发生了泄漏。
ObjectWatcher.class
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
//创建KeyedWeakReference,KeyedWeakReference继承于WeakReference。
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
// 根据key加入map
watchedObjects[key] = reference
//会延迟5s
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
@Synchronized private fun moveToRetained(key: String) {
//根据queue移除map里的对象
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
//如果未能移除,可能发生了泄漏。通知注册了的Listener
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
var ref: KeyedWeakReference?
do {
//如果activity被回收了,包含该activity的KeyedWeakReference就会在该queue中,根据queue把map中对象移除
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
}
2.3 发起GC再次检测
ObjectWatcher 会回调到InternalLeakCanary的scheduleRetainedObjectCheck方法,最后调用checkRetainedObjects方法检测未回收对象数,触发GC,发起Dump。
InternalLeakCanary.class
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
HeapDumpTrigger.class
private fun checkRetainedObjects() {
...
//还存在的对象数量
var retainedReferenceCount = objectWatcher.retainedObjectCount
//触发GC,再获取剩余对象数
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
//小于5个不去dump
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
...
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
3 分析处理内存泄漏
3.1 分析工具
- LeakCanary
- Memory Analyzer Tool:Android产生的hprof文件需要sdk hprof-conv工具转换一下
- Android Studio Profiler:可以导入hprof文件分析
3.2 根据引用链处理
- 使用LeakCanary检测到内存泄漏后,如果引用链清晰,根据引用链解决泄漏问题。
- 如果LeakCanary无法给出引用链,可以将hprof文件导出,导入Android Studio使用Profiler分析,根据操作定位有问题的页面,再根据Profiler找出该页面相关对象,如一个Activity在内存中有10个对象,则很可能发生了泄漏。在根据页面猜测可能有问题的代码,使用二分法,注释该部分代码,再次测试。
- 部分问题可能需要多次使用才能出现,可以使用Espresso来自动化测试一些界面。
4 常见可能发生内存泄漏问题
- 单例使用Activity的Context
- 不正确使用静态Toast,自定义样式的Toast时使用Activity的layoutInflater解析布局文件
- Handler的错误使用
- 动画没有调用cancel