Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 工作中遇到的Android內存優化問題(3)-leakcanary源碼解析

工作中遇到的Android內存優化問題(3)-leakcanary源碼解析

編輯:關於Android編程

今天我們來看一下一個內存洩漏檢測神器 leakcanary(https://github.com/square/leakcanary)

首先我們來看一下leakcanary的使用說明

\

就這麼多,只需要一行代碼,太簡單了,簡單得都有點懷疑它了。

我們來看一下一個簡單的例子,也是它官方源碼中提供的一個例子,這個因為太小了我就截了個圖

\

從例子中可以看到,AsyncTask執行了sleep操作,但是由於AsyncTask聲明為了一個內部匿名類,此類持有外部類的對象,導致用戶退出此Activity時,此Activity不能被gc回收,安裝此例子到手機,點擊START NEW ASYNCTASK,退出app,觀察手機,會彈出一個內存洩漏通知如下圖

\

很神奇吧,連洩漏的堆棧調用信息都能查到,比我們在前兩篇用到的工具方便多了

leakcanary很神奇,就像魔術一樣,我們很想知道它背後的運行機制,現在我們就來解析一下leakcanary的源碼。首先從我們應用Application入手,因為leakcanary在使用中只有一行代碼,我們就從這行代碼慢慢跟蹤一下源碼。

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

首先我們進入install方法,install方法調用了另一個install方法
  public static RefWatcher install(Application application) {
    return install(application, DisplayLeakService.class,
        AndroidExcludedRefs.createAppDefaults().build());
  }
  /**
   * Creates a {@link RefWatcher} that reports results to the provided service, and starts watching
   * activity references (on ICS+).
   */
  public static RefWatcher install(Application application,
                                   Class listenerServiceClass,
                                   ExcludedRefs excludedRefs) {
    if (isInAnalyzerProcess(application)) {
      return RefWatcher.DISABLED;
    }
    enableDisplayLeakActivity(application);
    //此Listener很重要,在後面會扮演重要角色
    HeapDump.Listener heapDumpListener =
            new ServiceHeapDumpListener(application, listenerServiceClass);
    //從名字我們就可以看出它是監視內存洩漏對象的
    RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
    //
    ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
    return refWatcher;
  }



接著進入installOnIcsPlus方法,此方法就到了關鍵的地方,能解開為什麼我們只用一個方法,就能監聽所有的內存洩漏
  public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
    if (SDK_INT < ICE_CREAM_SANDWICH) {
      // If you need to support Android < ICS, override onDestroy() in your base activity.
      return;
    }
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    activityRefWatcher.watchActivities();
  }
ActivityRefWtacher提供了一個方法 watchActivitys()
  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

Android4.0以上的Application中提供了registerActivityLifecycleCallbacks方法,此方法從名字就可以看出是監聽Activity生命周期的,我們再來看看參數lifecycleCallbacks的定義,
  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們只要調用了onDestroy方法,就會被回調方法onActivityDestroyed知道,然後傳入ActivityRefWatcher的方法 onActivityDestroyed中,此方法很簡單
  void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

refWatcher就是我們上面install方法中創建的,進入watch方法
 public void watch(Object watchedReference, String referenceName) {
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    if (debuggerControl.isDebuggerAttached()) {
      return;
    }
    final long watchStartNanoTime = System.nanoTime();
    //首先生成了一個id,此id是用來唯一標識這個檢測對象的
    String key = UUID.randomUUID().toString();
    //將id存起來
    retainedKeys.add(key);
    //KeyWeakReference集成自 WeakReference(弱引用),WeakReference使用來跟蹤這個對象的,
    //弱引用大家都明白,它不會影響gc回收,構造WeakReference時,可以傳入一個ReferenceQueue,
    //這個ReferenceQueue的主要作用是當對象不可達時也就是可以被gc回收時,對象所對應的WeakReference就會被放入
    //ReferenceQueue中,只要檢測ReferenceQueue是否有我們的對象的WeakReference,就可以判斷對象是否可能洩漏
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    watchExecutor.execute(new Runnable() {
      @Override public void run() {
        //此方法就是為了確認對象是否可回收
        ensureGone(reference, watchStartNanoTime);
      }
    });
  }

進入ensureGone方法
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();

    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //此方法是循環ReferenceQueue,如果對象的ReferenceQueue在裡面,就從retainedKeys中移除對象的key,
    //因為此對象已經可回收,是安全的
    removeWeaklyReachableReferences();
    //判斷我們要檢測的reference是否還在retainedKeys中,如果不在說明已經被移除了,也就是可以被gc回收了
    if (gone(reference) || debuggerControl.isDebuggerAttached()) {
      return;
    }
    //執行垃圾回收,但是只是建議,並不是一定會執行
    gcTrigger.runGc();
    //再次從retainedKeys移除安全的key
    removeWeaklyReachableReferences();
    //如果此對象的WeakReference還是不能被回收,那麼此對象就有可能洩漏了,只是可能,因為gc在上一步可能沒有運行
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      //此方法獲得內存Heap的hprof文件,LeakCanary之所以這麼好用,主要是在這裡,它分析了hprof文件,來確認內存洩漏,
      //我們在上一篇也分析過hprof文件,原來LeakCanary也是分析這個文件,只是不需要人工分析了,LeakCanary用了一個自己
      //的開源hprof分析庫haha(https://github.com/square/haha)此庫是基於google的perflib.
      File heapDumpFile = heapDumper.dumpHeap();

      if (heapDumpFile == HeapDumper.NO_DUMP) {
        // Could not dump the heap, abort.
        return;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //heapdumpListener主要就是啟動服務分析hprof文件
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
  }

heapdumpListener在前面創建的時候是一個ServiceHeapDumpListener對象,進入此對象的analyze方法
  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }

  public static void runAnalysis(Context context, HeapDump heapDump,
      Class listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

啟動服務分析hprof文件,接著我們來看看這個服務
  @Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    //分析hprof的核心類
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    //檢查我們的對象是否內存洩漏
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
進入checkForLeak方法
 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);
      //解析過程,是基於google的perflib庫,根據hprof的格式進行解析,這裡就不展開看了
      Snapshot snapshot = parser.parse();
      //分析結果進行去重
      deduplicateGcRoots(snapshot);
      //此方法就是根據我們需要檢測的類的key,查詢解析結果中是否有我們的對象,獲取解析結果中我們檢測的對象
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);
      //此對象不存在表示已經被gc清除了,不存在洩露因此返回無洩漏
      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
      }
      //此對象存在也不能也不能確認它內存洩漏了,要檢測此對象的gc root
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

我們重點看一下findLeakingReference方法
  private Instance findLeakingReference(String key, Snapshot snapshot) {
    //因為需要檢測的類都構造了一個KeyedWeakReference,因此先找到KeyedWeakReference,就可以找到我們的對象
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List keysFound = new ArrayList<>();
    //循環所有KeyedWeakReference實例
    for (Instance instance : refClass.getInstancesList()) {
      List values = classInstanceValues(instance);
      //找到KeyedWeakReference裡面的key值,此值在我們前面傳入的對象唯一標示
      String keyCandidate = asString(fieldValue(values, "key"));
      //當key值相等時就表示是我們的檢測對象
      if (keyCandidate.equals(key)) {
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }
最後一步,也是最核心的方法,確認是否內存洩漏,和我們手動分析hprof的方法幾乎相同
  private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) {

    //這兩行代碼是判斷內存洩露的關鍵,我們在上篇中分析hprof文件,判斷內存洩漏
    //判斷的依據是展開調用到gc root,所謂gc root,就是不能被gc回收的對象,
    //gc root有很多類型,我們只要關注兩種類型1.此對象是靜態 2.此對象被其他線程使用,並且其他線程正在運行,沒有結束
    //pathFinder.findPath方法中也就是判斷這兩種情況
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    // 找不到引起內存洩漏的gc root,就表示此對象未洩漏
    // 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();

    retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);

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

核心的代碼我們已經看完了,是不是有一種豁然開朗的感覺,這就是好的軟件,將重復繁瑣的工作封裝起來,只給我們留下一個兩行的使用說明
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved