Android组件之LeakCanary2.0解析

在这里插入图片描述

前言

leakcanary 的官方网址:https://square.github.io/leakcanary/

LeakCanary使用

引用

升级到2.0之后,使用起来非常简单,只需要在build.gradle中添加依赖
注意这里不要直接使用implementation,使用implementation虽然不会在正式版中弹出内存泄漏的弹窗(因为LeakCanary内部做了限制),但是会增大约1mb的安装包体积

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'

在自定义buildtype中设置debuggable true,并设置测试版本的签名文件

debug{
    debuggable true
    ...
}

模拟内存泄漏

*(1) 新建一个单例类

public class SingleInstance {

    private Context context;
    private SingleInstance(Context context){
        this.context = context;
    }
    public static class Hodle {
        private static SingleInstance singleInstance;
        public static SingleInstance newInstance(Context context){
            if(singleInstance == null){
                singleInstance = new SingleInstance(context);
            }
            return singleInstance;
        }
    }
}
  • (2)SecondActivity的引用加入到单例中
public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        SingleInstance.Holder.newInstance(this);
    }
}
  • (3)当按back返回键时,SecondActivity会被销毁,但是引用还被单例持有,这时候SecondActivity 会出现内存泄漏。

  • LeakCanary 监测到内存泄漏会发送一个通知,点开通知可以看到详细的内存泄露堆栈信息。

  • 同样也可以在Logcat中看到。

LeakCanary 原理

1.0 原理

在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。
随后,会向主线程的MessageQueue添加一个IdleHandler,用于在idle时触发一个发生在HandlerThread的等待5秒后开始检测内存泄漏的代码。
这段代码首先会判断是否对象出现在引用队列中,如果有,则说明没有内存泄漏,结束。否则,调用Runtime.getRuntime().gc()进行GC,等待100ms后再次判断是否已经出现在引用队列中,若还没有被出现,那么说明有内存泄漏,开始dump hprof

2.0 原理

在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。
随后,会向主线程的抛出一个5秒后执行的Runnable,用于检测内存泄漏。
这段代码首先会将引用队列中出现的对象从观察对象数组中移除,然后再判断要观察的此对象是否存在。若不存在,则说明没有内存泄漏,结束。否则,就说明可能出现了内存泄漏,会调用Runtime.getRuntime().gc()进行GC,等待100ms后再次根据引用队列判断,若仍然出现在引用队列中,那么说明有内存泄漏,此时根据内存泄漏的个数弹出通知或者开始dump hprof。

  • 思考1:如何判断一个对象是否被回收
    如果一个对象除了弱引用以外,没有被其他对象所引用,当发生GC时,这个弱引用对象就会被回收,并且被回收掉的对象会被存放到ReferenceQueue中,所以当ReferenceQueue中有这个对象就代表这个对象已经被回收,反之就是没有被回收

  • 思考2: 这里为什么要延迟五秒执行任务
    我们都知道GC不是即时的, 页面销毁后预留5秒的时间给GC操作, 再后续分析引用泄露, 避免无效的分析

安装注册原理

  • Activity
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)

  • Fragment
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)

通过向Application注册registerActivityLifecycleCallbacks从而获取每个启动的Activity,然后

对于每个Activity,在其onDestroy方法调用之后,调用objectWatcher.watch观察这个Activity

对于Fragment而言,由于Fragment需要依附于Activity,且需要从Activity中获取FragmentManager,然后通过其registerFragmentLifecycleCallbacks方法观察Fragment,这样在Fragment调用onDestroyView和onDestory之后就能观察Fragment的View或者Fragment本身。

内存泄漏判定

ObjectWatcher.watch(Any) ,Activity、Fragment的View、Fragment都是由该方法进行观察的。

/**
  * Identical to [watch] with an empty string reference name.
  */
@Synchronized fun watch(watchedObject: Any) {
  watch(watchedObject, "")
}

/**
  * Watches the provided [watchedObject].
  *
  * @param name A logical identifier for the watched object.
  */
@Synchronized fun watch(
  watchedObject: Any,
  name: String
) {
  if (!isEnabled()) {
    return
  }
  // 将ReferenceQueue中出现的弱引用移除
  // 这是一个出现频率很高的方法,也是内存泄漏检测的关键点之一
  removeWeaklyReachableObjects()
  val key = UUID.randomUUID()
      .toString()
  // 记下观测开始的时间
  val watchUptimeMillis = clock.uptimeMillis()
  // 这里创建了一个自定义的弱引用,且调用了基类的WeakReference<Any>(referent, referenceQueue)构造器
  // 这样的话,弱引用被回收之前会出现在ReferenceQueue中
  val reference =
    KeyedWeakReference(watchedObject, key, name, watchUptimeMillis, queue)
  SharkLog.d {
      "Watching " +
          (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
          (if (name.isNotEmpty()) " named $name" else "") +
          " with key $key"
  }

  // 将key-reference保存到map中
  watchedObjects[key] = reference
  // 主线程5秒之后执行moveToRetained(key)方法
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

LeakCanary的关键代码之一: (1)将要观测的对象使用WeakReference保存起来,并在构造时传入一个ReferenceQueue,这样待观测的对象在被回收之前,会出现在ReferenceQueue中。 (2) 5秒钟之后再检查一下是否出现在了引用队列中,若出现了,则没有泄露。

为什么会是5S,这里猜测与Android GC有关。在Activity.H中,收到GC_WHEN_IDLE消息时会进行Looper.myQueue().addIdleHandler(mGcIdler),而mGcIdler最后会触发doGcIfNeeded操作,在该方法中会判断上次GC与现在时间的差值,而这个值就是MIN_TIME_BETWEEN_GCS = 5*1000。

弱引用入队列发生在终结函数或者GC发生之前。

  • removeWeaklyReachableObjects()
    将引用队列中出现的对象从map中移除,因为它们没有发生内存泄漏。
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 {
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}
  • moveToRetained
@Synchronized private fun moveToRetained(key: String) {
  removeWeaklyReachableObjects()
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}

5秒钟到了,还是先将引用队列中出现的对象从map中移除,因为它们没有内存泄漏。然后判断key还在不在map中,如果在的话,说明可能发生了内存泄漏。此时记下内存泄漏发生的时间,即更新retainedUptimeMillis字段,然后通知所有的对象,内存泄漏发生了。

hprof文件生成与解析

解析hprof文件,LeakCanary2使用的是Shark

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值