一步步拆解 LeakCanary

作者: 程序员徐公 | 来源:发表于2018-07-04 20:07 被阅读76次

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

    java 源码系列 - 带你读懂 Reference 和 ReferenceQueue

    https://blog.csdn.net/gdutxiaoxu/article/details/80738581

    一步步拆解 LeakCanary

    https://blog.csdn.net/gdutxiaoxu/article/details/80752876

    前言

    内存泄露,一直是我们性能优化方面的重点。今天,就让我们一起来拆解 LeakCanary,一步步理解它的原理

    原理概览

    讲解 LeakCannary 原理之前,我们先来说一下它的主要原理,给大家吃颗定心丸,其实挺简单的,大概可以分为以下几步:

    • 监听 Activity 的生命周期
    • 在 onDestroy 的时候,创建相应的 Refrence 和 RefrenceQueue,并启动后台进程去检测
    • 一段时间之后,从 RefrenceQueue 读取,若读取不到相应 activity 的 Refrence,有可能发生泄露了,这个时候,再促发 gc,一段时间之后,再去读取,若在从 RefrenceQueue 还是读取不到相应 activity 的 refrence,可以断定是发生内存泄露了
    • 发生内存泄露之后,dump,分析 hprof 文件,找到泄露路径(使用 haha 库分析),发送到通知栏

    原理分析

    LeakCanary#Install

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

    listenerServiceClass 方法

    public AndroidRefWatcherBuilder listenerServiceClass(
        Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
      return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
    }
    
    public final class ServiceHeapDumpListener implements HeapDump.Listener {
    
      private final Context context;
      private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
    
      public ServiceHeapDumpListener(Context context,
          Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
         // 启动后台服务监听
        setEnabled(context, listenerServiceClass, true);
        // 启动 HeapAnalyzerService ,用来分析 dump 文件
        setEnabled(context, HeapAnalyzerService.class, true);
        this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
        this.context = checkNotNull(context, "context").getApplicationContext();
      }
    
      ----  
    }
    

    listenerServiceClass() 方法绑定了一个后台服务 DisplayLeakService,这个服务主要用来分析内存泄漏结果并发送通知。你可以继承并重写这个类来进行一些自定义操作,比如上传分析结果等。

    RefWatcherBuilder.excludedRefs

    public final T excludedRefs(ExcludedRefs excludedRefs) {
      this.excludedRefs = excludedRefs;
      return self();
    }
    AndroidExcludedRefs.java
    /**
     * This returns the references in the leak path that can be ignored for app developers. This
     * doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs
     * in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app
     * developers except by resorting to serious hacks, so we remove the noise caused by those leaks.
     */
    public static ExcludedRefs.Builder createAppDefaults() {
      return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
    }
    
    public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
      ExcludedRefs.Builder excluded = ExcludedRefs.builder();
      for (AndroidExcludedRefs ref : refs) {
        if (ref.applies) {
          ref.add(excluded);
          ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
        }
      }
      return excluded;
    }
    

    excludedRefs() 方法定义了一些对于开发者可以忽略的路径,意思就是即使这里发生了内存泄漏,LeakCanary 也不会弹出通知。这大多是系统 Bug 导致的,无需用户进行处理。

    AndroidRefWatcherBuilder.buildAndInstall

    buildAndInstall 所做的工作,调用 build 构建 refWatcher,判断 refWatcher 是否 DISABLED,若不是 DISABLED 状态,调用 install 方法,并将 refWatcher 返回回去

    /**
     * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
     */
    public RefWatcher buildAndInstall() {
      // 构建 refWatcher 对象
      RefWatcher refWatcher = build();
      // 判断是否 DISABLED,若不是 DISABLED 状态,调用 
      if (refWatcher != DISABLED) {
        LeakCanary.enableDisplayLeakActivity(context);
        ActivityRefWatcher.install((Application) context, refWatcher);
      }
      return refWatcher;
    }
    

    了解 build 方法 之前,我们先来看一下 RefWatcherBuilder 是什么东东?

    RefWatcherBuilder

    public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
    
      private ExcludedRefs excludedRefs;
      private HeapDump.Listener heapDumpListener;
      private DebuggerControl debuggerControl;
      private HeapDumper heapDumper;
      private WatchExecutor watchExecutor;
      private GcTrigger gcTrigger;
    
    
    
      /** Creates a {@link RefWatcher}. */
      public final RefWatcher build() {
        if (isDisabled()) {
          return RefWatcher.DISABLED;
        }
    
        ExcludedRefs excludedRefs = this.excludedRefs;
        if (excludedRefs == null) {
          excludedRefs = defaultExcludedRefs();
        }
    
        HeapDump.Listener heapDumpListener = this.heapDumpListener;
        if (heapDumpListener == null) {
          heapDumpListener = defaultHeapDumpListener();
        }
    
        DebuggerControl debuggerControl = this.debuggerControl;
        if (debuggerControl == null) {
          debuggerControl = defaultDebuggerControl();
        }
    
        HeapDumper heapDumper = this.heapDumper;
        if (heapDumper == null) {
          heapDumper = defaultHeapDumper();
        }
    
        WatchExecutor watchExecutor = this.watchExecutor;
        if (watchExecutor == null) {
          watchExecutor = defaultWatchExecutor();
        }
    
        GcTrigger gcTrigger = this.gcTrigger;
        if (gcTrigger == null) {
          gcTrigger = defaultGcTrigger();
        }
    
        return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
            excludedRefs);
      }
      
      ----
    
    
    

    build 方法看到这里你是不是有一种很眼熟的感觉,没错,它运用了建造者模式,与我们 Android 中的 AlertDialog.build 同出一辙。 建造者模式(Builder)及其应用

    RefWatcherBuilder 主要有几个重要的成员变量

    • watchExecutor : 线程控制器,在 onDestroy() 之后并且主线程空闲时执行内存泄漏检测
    • debuggerControl : 判断是否处于调试模式,调试模式中不会进行内存泄漏检测
    • gcTrigger : 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 之后会再检测一次,仍然泄漏的判定为内存泄漏,进行后续操作
    • heapDumper : dump 内存泄漏处的 heap 信息,写入 hprof 文件
    • heapDumpListener : 解析完 hprof 文件,进行回调,并通知 DisplayLeakService 弹出提醒
    • excludedRefs : 排除可以忽略的泄漏路径

    接下来,我们一起来看一下 ActivityRefWatcher.install 方法

    ActivityRefWatcher.install((Application) context, refWatcher);
    
    public final class ActivityRefWatcher {
    
      /** @deprecated Use {@link #install(Application, RefWatcher)}. */
      @Deprecated
      public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
        install(application, refWatcher);
      }
    
      public static void install(Application application, RefWatcher refWatcher) {
        new ActivityRefWatcher(application, refWatcher).watchActivities();
      }
    
      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);
            }
          };
    
      private final Application application;
      private final RefWatcher refWatcher;
    
      /**
       * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
       * after they have been destroyed.
       */
      public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
        this.application = checkNotNull(application, "application");
        this.refWatcher = checkNotNull(refWatcher, "refWatcher");
      }
    
      void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
      }
    
      public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    
      public void stopWatchingActivities() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    }
    

    install 来说,主要做以下事情

    • 创建 ActivityRefWatcher,并调用 watchActivities 监听 activity 的生命周期
    • 在 activity 被销毁的时候,会回调 lifecycleCallbacks 的 onActivityDestroyed 方法,这时候会调用 onActivityDestroyed 去分析,而 onActivityDestroyed 方法又会回调 refWatcher.watch(activity)

    我们回到 refWatcher.watch 方法

    public void watch(Object watchedReference) {
      watch(watchedReference, "");
    }
    
    /**
     * Watches the provided references and checks if it can be GCed. This method is non blocking,
     * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
     * with.
     *
     * @param referenceName An logical identifier for the watched object.
     */
    public void watch(Object watchedReference, String referenceName) {
      if (this == DISABLED) {
        return;
      }
      checkNotNull(watchedReference, "watchedReference");
      checkNotNull(referenceName, "referenceName");
      final long watchStartNanoTime = System.nanoTime();
      // 保证 key 的唯一性
      String key = UUID.randomUUID().toString();
      // 添加到 set 集合中
      retainedKeys.add(key);
      // 穿件 KeyedWeakReference 对象
      final KeyedWeakReference reference =
          new KeyedWeakReference(watchedReference, key, referenceName, queue);
    
      ensureGoneAsync(watchStartNanoTime, reference);
    }
    
    
    
    • retainedKeys : 一个 Set<String> 集合,每个检测的对象都对应着一个唯一的 key,存储在 retainedKeys 中
    • KeyedWeakReference : 自定义的弱引用,持有检测对象和对用的 key 值

    我们先来看一下 KeyedWeakReference ,可以看到 KeyedWeakReference 继承于 WeakReference,并定义了 key,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");
      }
    }
    
    • key 对应的 key 值名称
    • referenceQueue 引用队列,当结合 Refrence 使用的时候,垃圾回收器回收的时候,会把相应的对象加入到 refrenceQueue 中。

    弱引用和引用队列 ReferenceQueue 联合使用时,如果弱引用持有的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference 持有的 Activity 对象如果被垃圾回收,该对象就会加入到引用队列 queue 中。具体的可以参考我的这一篇博客 java 源码系列 - 带你读懂 Reference 和 ReferenceQueue

    ensureGoneAsync 方法

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

    ensureGoneAsync 这个方法,在 watchExecutor 的回调里面执行了 ensureGone 方法,watchExecutor 是 AndroidWatchExecutor 的实例。

    接下来,我们一起来看一下 watchExecutor,主要关注 execute 方法

    watchExecutor

    public final class AndroidWatchExecutor implements WatchExecutor {
    
      static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
      private final Handler mainHandler;
      private final Handler backgroundHandler;
      private final long initialDelayMillis;
      private final long maxBackoffFactor;
    
      public AndroidWatchExecutor(long initialDelayMillis) {
        mainHandler = new Handler(Looper.getMainLooper());
        HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
        handlerThread.start();
        backgroundHandler = new Handler(handlerThread.getLooper());
        this.initialDelayMillis = initialDelayMillis;
        maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
      }
    
      @Override public void execute(Retryable retryable) {
        // 当前线程是主线程
        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
          waitForIdle(retryable, 0);
        } else { // 当前线程不是主线程
          postWaitForIdle(retryable, 0);
        }
      }
    
       --------
    }
    
    

    execute 方法,首先判断是否是主线程,如果是主线程,调用 waitForIdle 方法,等待空闲的时候执行,如果不是主线程,调用 postWaitForIdle 方法。我们一起来看一下 postWaitForIdle 和 waitForIdle 方法。

      // 调用 mainHandler 的 post 方法,,确保在主线程中执行
      void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
        mainHandler.post(new Runnable() {
          @Override public void run() {
            waitForIdle(retryable, failedAttempts);
          }
        });
      }
    
     // 当当前线程 looper 空闲的时候执行
      void waitForIdle(final Retryable retryable, final int failedAttempts) {
        // This needs to be called from the main thread.
        // 当 looper 空闲的时候,会回调 queueIdle 方法
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            postToBackgroundWithDelay(retryable, failedAttempts);
            return false;
          }
        });
      }
    

    可以看到 postWaitForIdle 方法其实是 调用 mainHandler 的 post 方法,,确保在主线程中执行,之后再 runnable 的 run 方法在调用 waitForIdle 方法。而 waitForIdle 方法是在等当前 looper 空闲之后,执行 postToBackgroundWithDelay 方法

      void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
      // 取 Math.pow(2, failedAttempts), maxBackoffFactor 的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,
      // 第一次执行的时候 failedAttempts 是 0 ,所以 exponentialBackoffFactor 是1
        long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
        // initialDelayMillis 的默认值是 5
        long delayMillis = initialDelayMillis * exponentialBackoffFactor;
        // 所以第一次延迟执行的时候是 5s,若
        backgroundHandler.postDelayed(new Runnable() {
          @Override public void run() {
            Retryable.Result result = retryable.run();
            // 过 result == RETRY,再次调用 postWaitForIdle,下一次的 delayMillis= 上一次的  delayMillis *2;
            // 正常情况下,不会返回 RETRY,当 heapDumpFile == RETRY_LATER (即 dump heap 失败的时候),会返回 RETRY
            if (result == RETRY) {
              postWaitForIdle(retryable, failedAttempts + 1);
            }
          }
        }, delayMillis);
      }
    

    postToBackgroundWithDelay 方法有点类似递归,正常情况下,若 retryable.run() 返回的结果不等于 RETRY,只会执行一次。若 retryable.run() 返回 RETRY,则会执行多次,退出的条件是 retryable.run() 返回结果不等于 RETRY;

    delay 的时间 取 Math.pow(2, failedAttempts), maxBackoffFactor 两个数的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,而,第一次执行的时候 failedAttempts 是 0 ,所以 exponentialBackoffFactor 是 1,即 delayMillis = initialDelayMillis * exponentialBackoffFactor= 5*1=5;

    因此,综合上面的例子,第一次执行的时间是 activity destroy 之后 5s。

    OK,我们回到 ensureGone 方法,这才是我们的重点

    @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
      long gcStartNanoTime = System.nanoTime();
      long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    
       // 移除已经被回收的引用
      removeWeaklyReachableReferences();
    
      if (debuggerControl.isDebuggerAttached()) {
        // The debugger can create false leaks.
        return RETRY;
      }
      // 判断 reference,即 activity 是否内回收了,若被回收了,直接返回
      if (gone(reference)) {
        return DONE;
      }
      // 调用 gc 方法进行垃圾回收
      gcTrigger.runGc();
       // 移除已经被回收的引用
      removeWeaklyReachableReferences();
      // activity 还没有被回收,证明发生内存泄露
      if (!gone(reference)) {
        long startDumpHeap = System.nanoTime();
        long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
        // dump heap,并生成相应的 hprof 文件
        File heapDumpFile = heapDumper.dumpHeap();
        
        if (heapDumpFile == RETRY_LATER) {// dump the heap 失败的时候
          // Could not dump the heap.
          return RETRY;
        }
        long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
        // 分析 hprof 文件
        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;
      // 遍历 queue ,并从 retainedKeys set 集合中移除
      while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        retainedKeys.remove(ref.key);
      }
    }
    
    
    

    gone(reference) 方法,判断 retainedKeys set 集合,是否还含有 reference,若没有,证明已经被回收了;若含有,可能已经发生内存泄露。因为我们知道 refrence 被回收的时候,会被加进 queue 里面,值调用 gone 方法判断的时候,我们已经遍历 queue 移除掉 retainedKeys 里面的 refrence,若含有,证明 refrence 没有被回收,之所以说可能发生内存泄露,是因为 gc 回收器可能还没有回收。

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

    gcTrigger.runGc() 的主要作用是促发 gc,进行回收。

      GcTrigger DEFAULT = new GcTrigger() {
        @Override public void runGc() {
          // Code taken from AOSP FinalizationTest:
          // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
          // java/lang/ref/FinalizationTester.java
          // System.gc() does not garbage collect every time. Runtime.gc() is
          // more likely to perfom a gc.
          Runtime.getRuntime().gc();
          enqueueReferences();
          System.runFinalization();
        }
    
        private void enqueueReferences() {
          // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
          // references to the appropriate queues.
          try {
            Thread.sleep(100);
          } catch (InterruptedException e) {
            throw new AssertionError();
          }
        }
      };
    

    ok,我们在回到 ensureGoneAsync 方法,整理一下它的流程

    • Activity onDestroy 5s 之后,检测 activity 的弱引用 refrence 有没有被回收,若被回收,证明没有发生内存泄露,若没有被回收,继续下面流程
    • 调用 gcTrigger.runGc() 促发垃圾回收机器进行回收
    • 再次检测 activity 的弱引用 refrence 有没有被回收,若被回收,证明没有发生内存泄露,若没有被回收,则认为发生内存泄露
    • dump heap,生成 hprof。
    • 分析 hprof 文件,找到泄露路径,发送到通知栏

    关于如何 dump 和 如何解析hprof

    关于如何 dump

    这里主要是调用 AndroidHeapDumper 的 dumpHeap 方法,而里面比较重要的是调用 Debug.dumpHprofData 生成 hprof 文件。

    AndroidHeapDumper#dumpHeap

    @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
    @Override public File dumpHeap() {
      File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
    
      if (heapDumpFile == RETRY_LATER) {
        return RETRY_LATER;
      }
    
      FutureResult<Toast> waitingForToast = new FutureResult<>();
      showToast(waitingForToast);
    
      if (!waitingForToast.wait(5, SECONDS)) {
        CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
        return RETRY_LATER;
      }
    
      Toast toast = waitingForToast.get();
      try {
        Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
        cancelToast(toast);
        return heapDumpFile;
      } catch (Exception e) {
        CanaryLog.d(e, "Could not dump heap");
        // Abort heap dump
        return RETRY_LATER;
      }
    }
    

    如何解析hprof

    当发生了泄漏就会生成 HeapDump 对象然后就会进入下面这个方法去启动 HeapAnalyzerServiceService 来进行分析

    @Override public void analyze(HeapDump heapDump) {
        checkNotNull(heapDump, "heapDump");
        HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
      }
    

    关于如解析 hprof,请自行了解 haha 库的用法即原理

    public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
        long analysisStartNanoTime = System.nanoTime();
    
        if (!heapDumpFile.exists()) {
          Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
          return failure(exception, since(analysisStartNanoTime));
        }
    
        try {
          HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
          HprofParser parser = new HprofParser(buffer);
          Snapshot snapshot = parser.parse();
          deduplicateGcRoots(snapshot);
    
          Instance leakingRef = findLeakingReference(referenceKey, snapshot);
    
          // False alarm, weak reference was cleared in between key check and heap dump.
          if (leakingRef == null) {
            return noLeak(since(analysisStartNanoTime));
          }
    
          return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
        } catch (Throwable e) {
          return failure(e, since(analysisStartNanoTime));
        }
      }
    

    经过解析之后机会把数据传递到 DisplayLeakService ,Service 会根据传入进来的数据发送通知栏通知,当你点击对应的通知进入DisplayLeakActivity界面就能显示泄漏日志了。


    总结:

    LeakCanary 的原理总结如下

    • 监听 Activity 的生命周期
    • 在 onDestroy 的时候,创建相应的 Refrence 和 RefrenceQueue,并启动后台进程去检测
    • 一段时间之后,从 RefrenceQueue 读取,若读取不到相应 activity 的 Refrence,有可能发生泄露了,这个时候,再促发 gc,一段时间之后,再去读取,若在从 RefrenceQueue 还是读取不到相应 activity 的 refrence,可以断定是发生内存泄露了
    • 发生内存泄露之后,dump,分析 hprof 文件,找到泄露路径(使用 haha 库分析)

    其中,比较重要的是如何确定是否发生内存泄露,而如何确定发生内存泄露最主要的原理是通过 Refrence 和 RefrenceQueue。悄悄地提醒你一下,面试必备。

    最后,用一张图片来表示 leakCannary 的执行流程,该图片来自 深入理解 Android 之 LeakCanary 源码解析

    image

    java 源码系列 - 带你读懂 Reference 和 ReferenceQueue

    https://blog.csdn.net/gdutxiaoxu/article/details/80738581

    一步步拆解 LeakCanary

    https://blog.csdn.net/gdutxiaoxu/article/details/80752876

    最后的最后

    卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。

    image

    相关文章

      网友评论

        本文标题:一步步拆解 LeakCanary

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