LeakCanary-内存泄漏检测神器简介

一、什么是内存泄漏?
在Android的开发中,经常听到“内存泄漏”这个词。“内存泄漏”就是一个对象已经不需要再使用了,但是因为其它的对象持有该对象的引用,导致它的内存不能被回收。那么在android开发过程中,有哪些比较常见的场景呢?
其实主要就是activity或者fragment内存泄漏了。
那么,泄漏的原因一般有哪些呢?
1、数据库游标忘记关闭了。
2、bitmap忘记回收
3、不当的使用handler
4、timer等不当的使用。
5、地图等
总之,归纳起来一句话来讲:

目前开发者遇到的内存泄漏普遍就是以上几种了,这里列举的仅仅是我遇到的比较多的场景,也不排除还有其他场景。

二、那么遇到内存泄漏一般是怎么追踪并解决问题的呢?
通常,开发者会利用android studio自带的工具进行分析:

1、让自己的app先跑上一段时间,在怀疑会产生内存泄漏的几个地方来回的多切换几次。
然后,打开着界面,在点击这里。


LeakCanary-内存泄漏检测神器简介_第1张图片
先GC

对,通常先GC一下,因为,内存泄漏并不会因为GC而消除,GC只是将一些不必要的干扰因素排除掉而已。
2、然后点GC右边的那个按钮,dump一下内存,来进行分析,dump之后,自动出现这个界面


LeakCanary-内存泄漏检测神器简介_第2张图片
dum的内存

为了方便查看,我们通常会根据包名Arrage by package分类查看,其实我们只关心自己应用包名先的activity,fragment之类的一些。
3、这里,右边会列举出实例的个数,通常出现几个实例的activity其实就是你该严重怀疑的对象了,那么
LeakCanary-内存泄漏检测神器简介_第3张图片
找引用树

根据这里的引用树,最终层层剥根,你就可以轻易发现最终是谁引用到了这个activity,而导致他泄漏了。
三,恩,回顾一下,这个过程,真是太麻烦了,那么,有没有一个神器可以不用这么麻烦,就帮我们发现内存泄漏,以及定位出原因呢?

答案显然是有的,他就是 LeakCanary

LeakCanary-内存泄漏检测神器简介_第4张图片
LeakCanary

1、leak canary的原理:

实际上就是有一个线程专门去分析内存图谱,然后,有一些列的lifecircler之类的钩子监听activity 的destory方法,如果一旦发现某个activity执行的 destroy发放,但是,他的实例还一直存在于内存中,那就判定这个activity泄漏了。
一下就是HeapAnalyzer的一段代码片段:

 /**
   * Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
   * and then computes the shortest strong reference path from that instance to the GC roots.
   */
  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));
    }
  }

private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) {

    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);

    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) {
      return noLeak(since(analysisStartNanoTime));
    }

    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

    String className = leakingRef.getClassObj().getClassName();

    // Side effect: computes retained size.
    snapshot.computeDominators();

    Instance leakingInstance = result.leakingNode.instance;

    long retainedSize = leakingInstance.getTotalRetainedSize();

    // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
    if (SDK_INT <= N_MR1) {
      retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
    }

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  }

其中,这段代码清晰表明:

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

如果一个该销毁的context还被持有,那么就是泄漏了。

2、leak canary 使用:
如果你使用android studio开发,那么相当简单

在build.gralde中添加
dependencies {
  debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
  releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}

然后:在你的Application中添加一下代码即可。
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...
  }
}

其中

releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

是一个空操作集合,表示,在生产环境下,不会进行内心泄漏检测,只是在开发调试环境起作用,为什么要这么做呢?很显然,一个是为了app流畅,内存泄漏分析是需要消耗性能的,第二个,用户其实对你的app内存泄漏并不关心,一般用户也不明白这是个什么鬼,你突然弹出一个提醒说某某activity泄漏了,用户也是一脸懵逼。

你可能感兴趣的:(LeakCanary-内存泄漏检测神器简介)