LeakCanary-內(nèi)存泄漏檢測(cè)神器簡(jiǎn)介

一、什么是內(nèi)存泄漏?
在Android的開(kāi)發(fā)中,經(jīng)常聽(tīng)到“內(nèi)存泄漏”這個(gè)詞?!皟?nèi)存泄漏”就是一個(gè)對(duì)象已經(jīng)不需要再使用了,但是因?yàn)槠渌膶?duì)象持有該對(duì)象的引用,導(dǎo)致它的內(nèi)存不能被回收。那么在android開(kāi)發(fā)過(guò)程中,有哪些比較常見(jiàn)的場(chǎng)景呢?
其實(shí)主要就是activity或者fragment內(nèi)存泄漏了。
那么,泄漏的原因一般有哪些呢?
1、數(shù)據(jù)庫(kù)游標(biāo)忘記關(guān)閉了。
2、bitmap忘記回收
3、不當(dāng)?shù)氖褂胔andler
4、timer等不當(dāng)?shù)氖褂谩?br> 5、地圖等
總之,歸納起來(lái)一句話來(lái)講:

目前開(kāi)發(fā)者遇到的內(nèi)存泄漏普遍就是以上幾種了,這里列舉的僅僅是我遇到的比較多的場(chǎng)景,也不排除還有其他場(chǎng)景。

二、那么遇到內(nèi)存泄漏一般是怎么追蹤并解決問(wèn)題的呢?
通常,開(kāi)發(fā)者會(huì)利用android studio自帶的工具進(jìn)行分析:

1、讓自己的app先跑上一段時(shí)間,在懷疑會(huì)產(chǎn)生內(nèi)存泄漏的幾個(gè)地方來(lái)回的多切換幾次。
然后,打開(kāi)著界面,在點(diǎn)擊這里。


先GC

對(duì),通常先GC一下,因?yàn)?,?nèi)存泄漏并不會(huì)因?yàn)镚C而消除,GC只是將一些不必要的干擾因素排除掉而已。
2、然后點(diǎn)GC右邊的那個(gè)按鈕,dump一下內(nèi)存,來(lái)進(jìn)行分析,dump之后,自動(dòng)出現(xiàn)這個(gè)界面


dum的內(nèi)存

為了方便查看,我們通常會(huì)根據(jù)包名Arrage by package分類(lèi)查看,其實(shí)我們只關(guān)心自己應(yīng)用包名先的activity,fragment之類(lèi)的一些。
3、這里,右邊會(huì)列舉出實(shí)例的個(gè)數(shù),通常出現(xiàn)幾個(gè)實(shí)例的activity其實(shí)就是你該嚴(yán)重懷疑的對(duì)象了,那么
找引用樹(shù)

根據(jù)這里的引用樹(shù),最終層層剝根,你就可以輕易發(fā)現(xiàn)最終是誰(shuí)引用到了這個(gè)activity,而導(dǎo)致他泄漏了。
三,恩,回顧一下,這個(gè)過(guò)程,真是太麻煩了,那么,有沒(méi)有一個(gè)神器可以不用這么麻煩,就幫我們發(fā)現(xiàn)內(nèi)存泄漏,以及定位出原因呢?

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

LeakCanary

1、leak canary的原理:

實(shí)際上就是有一個(gè)線程專(zhuān)門(mén)去分析內(nèi)存圖譜,然后,有一些列的lifecircler之類(lèi)的鉤子監(jiān)聽(tīng)activity 的destory方法,如果一旦發(fā)現(xiàn)某個(gè)activity執(zhí)行的 destroy發(fā)放,但是,他的實(shí)例還一直存在于內(nèi)存中,那就判定這個(gè)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);  

如果一個(gè)該銷(xiāo)毀的context還被持有,那么就是泄漏了。

2、leak canary 使用:
如果你使用android studio開(kāi)發(fā),那么相當(dāng)簡(jiǎn)單

在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'

是一個(gè)空操作集合,表示,在生產(chǎn)環(huán)境下,不會(huì)進(jìn)行內(nèi)心泄漏檢測(cè),只是在開(kāi)發(fā)調(diào)試環(huán)境起作用,為什么要這么做呢?很顯然,一個(gè)是為了app流暢,內(nèi)存泄漏分析是需要消耗性能的,第二個(gè),用戶其實(shí)對(duì)你的app內(nèi)存泄漏并不關(guān)心,一般用戶也不明白這是個(gè)什么鬼,你突然彈出一個(gè)提醒說(shuō)某某activity泄漏了,用戶也是一臉懵逼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容