LeakCanary源码分析

作者: Joker_Wan | 来源:发表于2020-07-20 18:06 被阅读0次

    1 LeakCanary简介

    LeakCanary 是 Square 公司的一个开源库。通过它可以在 App 运行过程中检测内存泄漏,当内存泄漏发生时会生成发生泄漏对象的引用链,并在手机中弹出通知栏告知程序开发人员。

    LeakCanary 两大核心

    1. 检测内存泄漏
    2. 分析内存泄漏对象的引用链

    LeakCanary 中对内存泄漏检测的核心原理就是基于 WeakReferenceReferenceQueue 实现的。

    WeakReference
    Java 中的 WeakReference 是弱引用类型,每当发生 GC 时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收。

    ReferenceQueue
    WeakReference 的构造函数可以传入 ReferenceQueue,当 WeakReference 指向的对象被垃圾回收器回收时,会把 WeakReference 放入 ReferenceQueue 中。

    2 LeakCanary 中对内存泄漏检测的核心原理

    1. 当一个 Activity 需要被回收时,就将其包装到一个 WeakReference 中,并且在 WeakReference 的构造器中传入自定义的 ReferenceQueue

    2. 给包装后的 WeakReference 做生成唯一标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录

    3. 主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set 中的记录也删除

    4. 经过上面 3 步之后,还保留在 Set 中的就是:应当被 GC 回收,但是实际还保留在内存中的对象,也就是发生泄漏了的对象。

    5. 然后结合dump memory的 hpof 文件, 用 haha开源库 分析出内存泄漏的对象的引用链

    3 LeakCanary 源码分析

    提示:分析的 LeakCanary 版本为:1.5.4

    我们知道一个可回收对象在 System.gc() 之后就应该被 GC 回收,但是 Android 系统何时回收 Activity 是未知的。一般我们认为,当 Activity 调用 onDestroy 方法时就说明这个 Activity 就已经处于无用状态了,可以被 GC 了。所以就需要监听每个 Activity 的 onDestroy 方法的调用。

    我们在 Application 中初始化 LeakCanary 代码如下

    public class MyApplication extends Application {
    
        private RefWatcher refWatcher;
    
        public static RefWatcher getRefWatcher(Context context) {
            MyApplication application = (MyApplication) context.getApplicationContext();
            return application.refWatcher;
        }
    
        @Override
        public void onCreate() {
          super.onCreate();
          if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
          }
          refWatcher = LeakCanary.install(this);
        }
    }
    

    从上面代码可以看出,LeakCanary 会首先判断当前进程是否是用于分析heap内存的而创建的进程,不是才进行LeakCanary的安装操作LeakCanary.install 方法返回的是 RefWatcher 对象,用于监测实例对象的引用状态。LeakCanary只检测了 Activity 的内存泄漏,若要检测 Fragment 的内存泄漏,则可以在 Fragment onDestroy() 方法中加入如下代码:

    MyApplication.getGetRefWatcher(activity).watch(this)
    

    我们看 LeakCanary.install(application)方法

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

    此方法主要是来构造 RefWatcher 对象,refWatcher 方法构造了一个 AndroidRefWatcherBuilder 对象,AndroidRefWatcherBuilder的构造方法中将传入的 context 转为 ApplicationContext,在接着传入一个 DisplayLeakService 的Class对象,它的作用是展示泄露分析的结果日志,然后会展示一个用于跳转到显示泄露界面DisplayLeakActivity的通知。在调用 buildAndInstall 方法前调用了 excludedRefs(AndroidExcludedRefs.createAppDefaults().build())方法,这个方法主要是指定了一系列需要忽略的场景,这些特殊的场景都被枚举在 AndroidExcludedRefs 中。因为有些特殊机型的系统本身就存在一些内存泄漏的情况,导致 Activity 不被回收,所以在检测内存泄漏时,需要将这些情况排除在外。继续看 AndroidRefWatcherBuilder 类的buildAndInstall()

      public RefWatcher buildAndInstall() {
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
          LeakCanary.enableDisplayLeakActivity(context);
          ActivityRefWatcher.install((Application) context, refWatcher);
        }
        return refWatcher;
      }
    

    需要监听每个 Activity 的 onDestroy 方法在 ActivityRefWatcher.install方法中,主要是通过注册 Android 系统提供的 ActivityLifecycleCallbacks,来监听 Activity 的生命周期方法的调用,代码如下:

      public static void install(Application application, RefWatcher refWatcher) {
        new ActivityRefWatcher(application, refWatcher).watchActivities();
      }
      
      public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    
      private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
          new Application.ActivityLifecycleCallbacks() {
            @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }
    
            @Override public void onActivityStarted(Activity activity) {
            }
    
            @Override public void onActivityResumed(Activity activity) {
            }
    
            @Override public void onActivityPaused(Activity activity) {
            }
    
            @Override public void onActivityStopped(Activity activity) {
            }
    
            @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
    
            @Override public void onActivityDestroyed(Activity activity) {
              ActivityRefWatcher.this.onActivityDestroyed(activity);
            }
          };
          
      void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
      }
    

    可以看出当监听到 Activity 的 onDestroy 方法后,会将其传给 RefWatcher 的 watch 方法。RefWatcher是 LeakCanary 的一个核心类,用来检测一个对象是否会发生内存泄漏。主要实现是在 watch 方法中

      public void watch(Object watchedReference) {
        watch(watchedReference, "");
      }
      
      public void watch(Object watchedReference, String referenceName) {
      
        // 省略部分无关代码...
        
        // 生成一个随机的字符串 key,这个 key 就是用来标识 WeakReference 的,就相当于给 WeakReference 打了个标签
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        
        // 将被检测对象包装到一个 WeakReference 中,并用上面代码生成的 key 标识
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
    
        // 开始执行检测操作
        ensureGoneAsync(watchStartNanoTime, reference);
      }
    

    注释中已经做了相关代码说明,此处直接看检测泄漏的方法ensureGoneAsync

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

    内存泄漏的检测与分析是比较消耗性能的,因此为了尽量不影响 UI 线程的渲染,在 ensureGoneAsync 方法中调用了 WatchExecutor 的 execute 方法来执行检测操作。

      @Override public void execute(Retryable retryable) {
        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
          waitForIdle(retryable, 0);
        } else {
          postWaitForIdle(retryable, 0);
        }
      }
      
        void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
        mainHandler.post(new Runnable() {
          @Override public void run() {
            waitForIdle(retryable, failedAttempts);
          }
        });
      }
      
        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;
          }
        });
      }
    

    如果是主线程,则直接调用 waitForIdle 方法,否则用 Handler post 一个 Runnable 到主线程中执行 waitForIdle 方法;waitForIdle 方法中向主线程 MessageQueue 中插入了一个 IdleHandler,IdleHandler 只会在主线程空闲时才会被 Looper 从队列中取出并执行,能够有效避免内存检测工作阻塞 UI 渲染。

    通过调用 addIdleHandler 方法也经常用来做 App 的启动优化,比如在 Application 的 onCreate 方法中经常做 3 方库的初始化工作。可以将优先级较低、暂时使用不到的 3 方库的初始化操作放到 IdleHandler 中,从而加快 Application 的启动过程。

    通过 WatchExecutor 执行了一个重载的方法 ensureGone

      @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
      Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
      
        // 省略部分无关代码...
        
        // 遍历 ReferenceQueue 中所有的元素,并根据每个元素中的 key,相应的将集合 retainedKeys 中的元素也删除
        removeWeaklyReachableReferences();
    
        // 规避调试模式
        if (debuggerControl.isDebuggerAttached()) {
          // The debugger can create false leaks.
          return RETRY;
        }
        
        // 检测是否已经回收
        if (gone(reference)) {
          return DONE;
        }
        
        // 如果没有被回收则手动执行GC
        gcTrigger.runGc();
    
        // 再次检测并移除已被回收的 references
        removeWeaklyReachableReferences();
    
        // 判断集合 retainedKeys 是否还包含被检测对象的弱引用,包含则说明被检测对象并发生了内存泄漏
        if (!gone(reference)) {
        
          // 还没有被回收,则dump堆信息
          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);
          
          // 调起分析进程进行分析
          heapdumpListener.analyze(
              new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                  gcDurationMs, heapDumpDurationMs));
        }
        return DONE;
      }
    
    

    我们先来看下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);
        }
      }
    

    这个方法从 retainedKeys 中移除已经被回收的 WeakReference 的标志,继续看下gone(reference)方法

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

    这个方法用来判断 reference 是否被回收,在 retainedKeys 中包含此reference.key ,则说明 WeakReference 引用的对象没有被回收,也就是发生了内存泄漏。

    4 总结

    1. LeakCanary 中对内存泄漏检测的核心原理就是基于 WeakReference 和 ReferenceQueue 实现的
    2. LeakCanary 中监听 Activity 生命周期是 ActivityRefWatch 类通过注册 Android 系统提供的 ActivityLifecycleCallbacks来实现
    3. LeakCanary 主要检测内存泄漏的核心类:RefWatcher
    4. 通过向主线程 MessageQueue 中插入 IdleHandler 方式来优化内存泄漏的检测时机
    5. 在 LeakCanary 的初始化方法 install 中,通过 excludedRefs 方法统一规避特殊机型的内存泄漏

    相关文章

      网友评论

        本文标题:LeakCanary源码分析

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