LeakCanary从入门到源码分析

作者: 左手木亽 | 来源:发表于2017-07-13 19:03 被阅读404次

    “A small leak will sink a great ship.” - Benjamin Franklin

    千里之堤, 毁于蚁穴。 -- 《韩非子·喻老》

    LeakCanary是什么?可以从LeakCanary的github很容易的得到定义:
    Android|Java的内存检测库

    更多使用方法:https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

    简单集成

    在的build.gradle:

    dependencies {
       debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
       releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
       testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
     }
    

    在你的Applicationclass:

    public class ExampleApplication extends Application {
    
      @Override public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
          // This process is dedicated to LeakCanary for heap analysis.
          // You should not init your app in this process.
          return;
        }
        LeakCanary.install(this);
        // Normal app init code...
      }
    }
    

    然后什么都不用做,当出现内存泄漏的时候LeakCanary就会在你的通知栏发送一条,点进去就是泄漏日志。

    这里写图片描述

    Dump泄漏的hprof文件

    从初始化入口LeakCanary.install(this)可以很清楚的看到得到了一个AndroidRefWatcherBuilder对象

      public static RefWatcher install(Application application) {
        return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
            .buildAndInstall();
      }
    
      /** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
      public static AndroidRefWatcherBuilder refWatcher(Context context) {
        return new AndroidRefWatcherBuilder(context);
      }
    

    而这个对象extends RefWatcherBuilder进入RefWatcherBuilder可以看出这是一个采用构造者设计模式所以直接进入build方法进行查看

      /** 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);
      }
    

    HeapDumper可以从名字我们可以猜测用于dump hprof文件的、一样的我们点进去这个类发现是一个接口,所以找到他的实现类AndroidHeapDumper

      @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;
        }
      }
    

    稍微提及一下LeakDirectoryProvider这个类主要用于初始化SD卡存放LeakCanary生成的文件目录,大概位置于/sdcard/download/leakcanary-你的包名 回到我们的AndroidHeapDumper类、我们只需要关心一行代码

    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    

    采用Android自带的Debug来来doump hprof文件然后保存到LeakDirectoryProvider初始化的文件目录下

    什么时候Dump

    回到LeakCanary类中的buildAndInstall方法

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

    主要做两步
    第一步:enableDisplayLeakActivity进入这个方法把DisplayLeakActivity这个Activity组件恢复正常使用

      public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
          boolean enabled) {
        ComponentName component = new ComponentName(appContext, componentClass);
        PackageManager packageManager = appContext.getPackageManager();
        int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
        // Blocks on IPC.
        packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
      }
    

    第二步也是主要步骤进入ActivityRefWatcher类初始化Application.ActivityLifecycleCallbacks监听

      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);
            }
          };
    

    对的,就是监听当一个Activity执行onDestory的时候进行监听它

      void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
      }
    

    自然而然我们需要进入RefWatcher.watch这个方法去查看具体是怎么分析

      public void watch(Object watchedReference, String referenceName) {
        if (this == DISABLED) {
          return;
        }
        checkNotNull(watchedReference, "watchedReference");
        checkNotNull(referenceName, "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);
      }
    

    很巧妙的使用KeyedWeakReference弱引用,为了确保弱引用能被回收“偷偷”的跑了Runtime.getRuntime().gc()来实现对弱引用的回收

      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;
        }
        if (gone(reference)) {
          return DONE;
        }
        gcTrigger.runGc();
        removeWeaklyReachableReferences();
        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);
          heapdumpListener.analyze(
              new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                  gcDurationMs, heapDumpDurationMs));
        }
        return DONE;
      }
    

    我们再watch方法中每次都会把当前需要检测的对象或者说是Activity组件加入Set<String> retainedKeys这个Set容器中,然后系统就会调用gcTrigger.runGc()来回收KeyedWeakReference弱引用

      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();
          }
        }
      };
    

    那怎么确定这个Activity发生泄漏了呢?
    首先我们需要明白一个道理,每次初始化传入了一个ReferenceQueue这个队列是用来存放每当当前的弱引用被GC回收了,那么当前这个弱引用对象就会被存入到这个Queue中去,所以每次只要能从retainedKeys把当前的KeyedWeakReference弱引用对应的key移除那么就证明没有发生泄漏,而当泄漏的话queue就没有供retainedKeys移除的key值

      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);
        }
      }
    
    
      private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
      }
    

    可以从这两个方法很好的看出来就是这么确认是否发生了泄漏。
    一旦发生了泄漏就开始Dump hprof文件了

        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);
          heapdumpListener.analyze(
              new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                  gcDurationMs, heapDumpDurationMs));
        }
    

    如何解析hprof

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

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

    然后这边想一提带过就好,因为这边使用的是Square haha库来进行解析dump下来的hprof文件

      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));
        }
      }
    

    想要了解更多可以去查看haha更多的使用方法。
    经过解析之后机会把数据传递到DisplayLeakService这是个Service会根据传入进来的数据发送通知栏通知,然后并存入数据,当你点击对应的通知进入DisplayLeakActivity界面就能显示泄漏日志了。

    差不多这就是LeakCanary如何监听泄漏、Dump泄漏、分析泄漏、显示泄漏的主要流程了,当然更重要的是发生了泄漏要懂得修复才是硬道理。

    相关文章

      网友评论

      • 布吉刀:首先我们需要明白一个道理,每次初始化传入了一个ReferenceQueue这个队列是用来存放每当当前的弱引用被GC回收了,那么当前这个弱引用对象就会被存入到这个Queue中去,所以每次只要能从retainedKeys把当前的KeyedWeakReference弱引用对应的key移除那么就证明没有发生泄漏,而当泄漏的话queue就没有供retainedKeys移除的key值

      本文标题: LeakCanary从入门到源码分析

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