美文网首页Android
(面试必备-源码分析系列)LeakCanary看这篇就行了

(面试必备-源码分析系列)LeakCanary看这篇就行了

作者: 蓝师傅_Android | 来源:发表于2019-06-01 12:13 被阅读0次

    关于LeakCanary的原理,基本是中高级Android岗位面试必问的问题了,
    LeakCanary文章一搜一大堆,公众号推文,各种长篇大论~

    应付面试,其实看这篇就够了。


    一、面试中的问题

    一般的中高级面试,都会问到性能优化,内存优化问题,而说到内存问题就肯定会问到内存泄漏问题,而一般的求职者二话不说,直接就上LeakCanary

    紧接着肯定是问:那你知道LeakCanary的原理是什么吗?
    可能还会问:你知道LeakCanary使用到的Idle机制吗?

    二、分析LeakCanary原理

    LeakCanary的集成非常简单,添加依赖,然后在Application主要是LeakCanary.install(this);
    这一句代码,不明白的看文档
    LeakCanary

    直接看install方法干了什么

      public static RefWatcher install(Application application) {
        return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
            .buildAndInstall();
      }
    

    直接看最后的 .buildAndInstall()

      public RefWatcher buildAndInstall() {
       ...
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
          if (watchActivities) {
            // 注释1
            ActivityRefWatcher.install(context, refWatcher);
          }
          if (watchFragments) {
            FragmentRefWatcher.Helper.install(context, refWatcher);
          }
        }
        LeakCanaryInternals.installedRefWatcher = refWatcher;
        return refWatcher;
      }
    

    检测Activity内存泄漏,看对应处理 ActivityRefWatcher.install(context, refWatcher);

      public static void install(Context context, RefWatcher refWatcher) {
        Application application = (Application) context.getApplicationContext();
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    
        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
      }
    
     private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
          new ActivityLifecycleCallbacksAdapter() {
            @Override public void onActivityDestroyed(Activity activity) {
              refWatcher.watch(activity);
            }
          };
    

    application.registerActivityLifecycleCallbacks 这里就是重点1了,监听Activity生命周期,然后在 onActivityDestroyed 回调中调用 refWatcher.watch(activity)

    然后继续跟

    private final Set<String> retainedKeys;
    private final ReferenceQueue<Object> queue;
    ...
    public void watch(Object watchedReference) {
        watch(watchedReference, "");
      }
    
    public void watch(Object watchedReference, String referenceName) {
        ...
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
    
        //重点
        ensureGoneAsync(watchStartNanoTime, reference);
      }
    

    watchedReference 是传过来的 activity
    retainedKeys 是一个Set,用来记录每一个加入检测的对象的key
    queue :ReferenceQueue<Object> 引用队列
    KeyedWeakReference 继承 WeakReference,保存key跟 name,
    name传的是空字符串符,可以忽略。

    final class KeyedWeakReference extends WeakReference<Object> {
      public final String key;
      public final String name;
    
      KeyedWeakReference(Object referent, String key, String name,
          ReferenceQueue<Object> referenceQueue) {
        super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
        this.key = checkNotNull(key, "key");
        this.name = checkNotNull(name, "name");
      }
    }
    

    这里有一个重要知识点,很多文章都没有说到

    弱引用和引用队列搭配使用,如果弱引用持有的对象被回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。也就是说如果KeyedWeakReference 持有的 Activity 对象被回收,该KeyedWeakReference就会加入到引用队列 queue 中。

    LeakCanary 就是利用这个原理。

    然后呢,创建了弱引用之后,就调用 ensureGoneAsync方法

    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        watchExecutor.execute(new Retryable() {
          @Override public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
          }
        });
      }
    

    这个watchExecutorAndroidWatchExecutor,看看AndroidWatchExecutor#execute方法

    @Override public void execute(Retryable retryable) {
        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
          waitForIdle(retryable, 0);
        } else {
          postWaitForIdle(retryable, 0);
        }
      }
    

    为什么不直接分析 ensureGone 方法,因为这里有个小知识点,看waitForIdle方法

    private void waitForIdle(final Retryable retryable, final int failedAttempts) {
        // This needs to be called from the main thread.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            //延时操作
            postToBackgroundWithDelay(retryable, failedAttempts);
            return false;
          }
        });
      }
    

    Looper.myQueue().addIdleHandler 会将一个任务添加到主线程消息队列的一个mIdleHandlers列表里,handler在消息队列中取不到消息时,也就是Handler空闲的时候,会去mIdleHandlers列表里取出任务执行。

    Handler消息机制不明白可以看(Android面试必知必会系列)Handler消息机制
    这篇文章,我在里面有介绍

    主线程空闲时候执行这个任务,具体干了什么,
    postToBackgroundWithDelay,顾名思义,后台延时处理

    private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
        long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
        long delayMillis = initialDelayMillis * exponentialBackoffFactor;
        backgroundHandler.postDelayed(new Runnable() {
          @Override public void run() {
            Retryable.Result result = retryable.run();
            if (result == RETRY) {
              postWaitForIdle(retryable, failedAttempts + 1);
            }
          }
        }, delayMillis);
      }
    

    这里的延时时间delayMillis跟过去是一个常量,5秒
    private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);
    也就是主线程空闲5秒后在后台线程执行 ensureGone 方法。

    ensureGone 方法

      Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        //注释1,移除弱引用
        removeWeaklyReachableReferences();
    
        //注释2,判断没有泄露
        if (gone(reference)) {
          return DONE;
        }
        //注释3,没有gone,可能是垃圾回收器没有及时回收,手动触发Gc
        gcTrigger.runGc();
        //注释4,继续移除引用
        removeWeaklyReachableReferences();
        //注释5,还是没有gone?那就是内存泄漏了
        if (!gone(reference)) {
          long startDumpHeap = System.nanoTime();
          long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
    
          File heapDumpFile = heapDumper.dumpHeap();
          if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
          }
          long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
    
          HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
              .referenceName(reference.name)
              .watchDurationMs(watchDurationMs)
              .gcDurationMs(gcDurationMs)
              .heapDumpDurationMs(heapDumpDurationMs)
              .build();
    
          heapdumpListener.analyze(heapDump);
        }
        return DONE;
      }
    

    如何判断内存泄漏的几个步骤

    1. removeWeaklyReachableReferences
    private void removeWeaklyReachableReferences() {
        // 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.
        KeyedWeakReference ref;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
          retainedKeys.remove(ref.key);
        }
      }
    

    遍历引用队列,如果里面不空,说明activity被回收了(为什么?上面说了一个重要知识点,不明白的回去看),获取key,在retainedKeys中移除对应的key(被回收则移除)

    1. if (gone(reference))
    private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
      }
    

    上面说了“被回收则移除”,那么如果retainedKeys中有对应的key,说明对象没有被回收

    1. gcTrigger.runGc();
      上面分析过 ensureGone 方法是在Activity退出后,主线程空闲时,再过5秒才执行,所以对象没有被回收,不一定就是内存泄漏,对象此时可能已经没有被引用了,正在等待下一次垃圾回收,所以手动触发GC,然后再重复 1和2 的操作,如果对象仍然没被回收,说明真的内存泄漏了。

    2. 判断内存泄漏后的处理
      if (!gone(reference)) {...}
      dump 出堆中的对象,用到HAHA这个库,采用可达性分析算法啥的,包括分析到哪个对象引起的内存泄漏,弹出通知,这些就不是本篇重点了,大家有兴趣可以自己去看。

    总结,回答面试中的问题:

    LeakCanary的原理是什么?(针对Activity来说)

    LeakCanary 通过监听Activity生命周期,在Activity onDestroy的时候,创建一个弱引用,key跟当前Activity绑定,将key保存到set里面,并且关联一个引用队列,然后在主线程空闲5秒后,开始检测是否内存泄漏,具体检测步骤:
    1:判断引用队列中是否有该Activity的引用,有则说明Activity被回收了,移除Set里面对应的key。
    2:判断Set里面是否有当前要检测的Activity的key,如果没有,说明Activity对象已经被回收了,没有内存泄漏。如果有,只能说明Activity对象还没有被回收,可能此时已经没有被引用,不一定是内存泄漏。
    3:手动触发GC,然后重复1和2操作,确定一下是不是真的内存泄漏。

    你知道LeakCanary中的Idle机制吗?

    在Activity onDestroy的时候,LeakCanary并没有马上去执行检测任务,而是将任务添加到消息队列的一个idle任务列表里,然后当Handler 在消息队列中获取不到消息,也就是主线程空闲的时候,会去idle任务列表里取任务出来执行。

    然后你就成功引导面试官问你Handler消息机制了,看这篇文章就对了
    (Android面试必知必会系列)Handler消息机制

    引用队列的小知识点参考:
    LeakCanary 源码解析

    更多文章,敬请期待。

    相关文章

      网友评论

        本文标题:(面试必备-源码分析系列)LeakCanary看这篇就行了

        本文链接:https://www.haomeiwen.com/subject/dcjotctx.html