LeakCanary从入门到源码分析

来源:互联网 发布:js的缺点 编辑:程序博客网 时间:2024/06/14 05:21

“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泄漏、分析泄漏、显示泄漏的主要流程了,当然更重要的是发生了泄漏要懂得修复才是硬道理。

原创粉丝点击