天天看點

LeakCanary源碼解析之檢測篇

源碼分析基于:1.6.3

對于Android開發者而言,記憶體洩漏是一種很常見的問題。LeakCanary就是捕獲記憶體洩漏的一把利器。我們在這裡就分析一下它的工作原理。

一、使用方法

使用方法就是我們在Application中添加代碼:

if (LeakCanary.isInAnalyzerProcess(this)) {

            // This process is dedicated to LeakCanary for heap analysis.

            // You should not onStart your app in this process.

            return;

        }

        LeakCanary.install(this);

 
           

看到這些估計你也會和我一樣,這樣我們就能捕獲到記憶體洩露了麼?不禁會産生這樣的疑問。我們就帶着疑問閱讀一下源碼尋找答案吧。

二、記憶體洩漏的檢測過程:

LeakCanary這個類是一個工具類,我們先看一下LeakCanary.install(this)這個方法的源碼:

/**

   * Creates a {@link RefWatcher} that works out of the box, and starts watching activity

   * references (on ICS+).

   */

  public static @NonNull RefWatcher install(@NonNull Application application) {

    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)

        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())

        .buildAndInstall();

  }
           

該方法中refWatcher(application)方法擷取AndroidRefWatcherBuilder執行個體對象;“.listenerServiceClass(DisplayLeakService.class)”方法是對記憶體洩漏分析結果的監聽;“.buildAndInstall()”方法用于生成RefWatcher對象,關于該方法的詳細代碼如下:

/**

   * Creates a {@link RefWatcher} instance and makes it available through {@link

   * LeakCanary#installedRefWatcher()}.

   *

   * Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.

   *

   * @throws UnsupportedOperationException if called more than once per Android process.

   */

  public @NonNull RefWatcher buildAndInstall() {

  //保證RefWatcher的唯一性

    if (LeakCanaryInternals.installedRefWatcher != null) {

      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");

    }

//如果沒有建立過RefWatcher,則建立一個;

    RefWatcher refWatcher = build();

//非空置對象(記憶體分析相關工具對象都是空殼對象)

    if (refWatcher != DISABLED) {

//是否允許使用内置展示頁面展示記憶體洩露資訊

      if (enableDisplayLeakActivity) {

        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);

      }

  //是否允許監測activity

      if (watchActivities) {

        ActivityRefWatcher.install(context, refWatcher);

      }

  //是否允許檢測fragment

      if (watchFragments) {

        FragmentRefWatcher.Helper.install(context, refWatcher);

      }

    }

//RefWatcher對象指派

    LeakCanaryInternals.installedRefWatcher = refWatcher;

    return refWatcher;

  }
           

在這裡RefWatcher就是記憶體洩漏的監控器。該方法就是生成RefWatcher對象,該架構預設允許檢測Activity和Fragment的記憶體洩漏,那麼它又是怎麼監控的呢?原理由是啥?以什麼為判斷記憶體洩漏的标準呢?

我們看一下ActivityRefWatcher中install()方法的邏輯:

public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {

    Application application = (Application) context.getApplicationContext();

    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

//Application注冊生命周期回調

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);

  }

 

 private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =

      new ActivityLifecycleCallbacksAdapter() {

        @Override public void onActivityDestroyed(Activity activity) {

          refWatcher.watch(activity);

        }

      };
           

lifecycleCallbacks是該類中的成員變量,通過注冊生命周期回調RefWatcher就可以通過頁面銷毀的方法回調觸發RefWatcher的watch()方法。我們先不管watch()方法中的具體邏輯,下面在分析一下fragment中的監測機制,下面我們分析一下FragmentRefWatcher.Helper這個内部類的源碼:

final class Helper {

 

    private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =

        "com.squareup.leakcanary.internal.SupportFragmentRefWatcher";

 

    public static void install(Context context, RefWatcher refWatcher) {

      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();

 

  //大于26的Android版本使用AndroidOFragmentRefWatcher

      if (SDK_INT >= O) {

        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));

      }

 

  //否則使用SupportFragmentRefWatcher

      try {

        Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);

        Constructor<?> constructor =

            fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);

        FragmentRefWatcher supportFragmentRefWatcher =

            (FragmentRefWatcher) constructor.newInstance(refWatcher);

        fragmentRefWatchers.add(supportFragmentRefWatcher);

      } catch (Exception ignored) {

      }

 

      if (fragmentRefWatchers.size() == 0) {

        return;

      }

 

      Helper helper = new Helper(fragmentRefWatchers);

 

      Application application = (Application) context.getApplicationContext();

  //注冊生命周期回調

      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);

    }

 

    private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =

        new ActivityLifecycleCallbacksAdapter() {

          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

            for (FragmentRefWatcher watcher : fragmentRefWatchers) {

//對應的FragmentManager注冊回調

              watcher.watchFragments(activity);

            }

          }

        };

 

    private final List<FragmentRefWatcher> fragmentRefWatchers;

 

    private Helper(List<FragmentRefWatcher> fragmentRefWatchers) {

      this.fragmentRefWatchers = fragmentRefWatchers;

    }

  }
           

該類中主要就是通過Activity的生命周期回調的onActivityCreated()方法,來擷取activity執行個體對象,具體需要分析watcher.watchFragments(activity)中的邏輯,由于存在兩種fragment對應的watcher,我們在這裡隻分析SupportFragmentRefWatcher中對應的代碼邏輯。

@Override public void watchFragments(Activity activity) {

    if (activity instanceof FragmentActivity) {

      FragmentManager supportFragmentManager =

          ((FragmentActivity) activity).getSupportFragmentManager();

      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);

    }

  }
           

方法中擷取FragmentManager執行個體對象,并注冊生命周期方法。對于Android版本大于26的,請閱讀AndroidOFragmentRefWatcher中的相關源碼,原理是一緻的。通過上面的源碼我們分析得知,對于Activity而言,是在activity的onDestory()方法内進行監測的;對于fragment而言,在生命周期回調方法内的onFragmentViewDestroyed()和onFragmentDestroyed()方法内進行監測,在這裡onFragmentViewDestroyed()方法對應于Fragment的onDestroyView()方法,onFragmentDestroyed()方法對應于Fragment#onDestroy()方法。

我們知道了LeakCanary的監測位置了,我們下面就通過源碼分析一下它是怎麼做到監測記憶體洩漏的。下面是RefWatcher類中的watch()方法的源碼:

/**

   * Watches the provided references and checks if it can be GCed. This method is non blocking,

   * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed

   * with.

   *

   * @param referenceName An logical identifier for the watched object.

   */

  public void watch(Object watchedReference, String referenceName) {

    if (this == DISABLED) {

      return;

    }

    checkNotNull(watchedReference, "watchedReference");

    checkNotNull(referenceName, "referenceName");

    final long watchStartNanoTime = System.nanoTime();

//生成随機的UUID作為該引用對象的key值

    String key = UUID.randomUUID().toString();

//添加的集合中

    retainedKeys.add(key);

//儲存引用資訊

    final KeyedWeakReference reference =

        new KeyedWeakReference(watchedReference, key, referenceName, queue);

 

//判斷引用是否存在

    ensureGoneAsync(watchStartNanoTime, reference);

  }
           

在該方法中,将之前生命周期中的方法中拿到的activity對象執行個體或者fragment對象執行個體進行儲存并進行相關的處理,在儲存時會生成一個唯一的key值,後面會通過key值進行相應的業務邏輯。下面我們看一下ensureGoneAsync()方法相關的邏輯,源碼如下:

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

    watchExecutor.execute(new Retryable() {

      @Override public Retryable.Result run() {

        return ensureGone(reference, watchStartNanoTime);

      }

    });

  }
           

該方法中watchExecutor是AndroidWatchExecutor的一個具體執行個體對象,最終是通過該實作類的execute()方法去執行引用相關的邏輯,這部分後面會分析。同時需要注意在excute()方法的内部類中run()方法會通過ensureGone()方法傳回Retryable.Result的值。這個值會在後面的分析中用到。我們在這裡先看一下AndroidWatchExecutor的具體實作。AndroidWatchExecutor是WatchExecutor接口的具體實作類,該接口主要用來執行Rertyable對象,并根據需要進行重試操作。我們看一下AndroidWatchExecutor的源碼:

/**

 * {@link WatchExecutor} suitable for watching Android reference leaks. This executor waits for the

 * main thread to be idle then posts to a serial background thread with the delay specified by

 * {@link AndroidRefWatcherBuilder#watchDelay(long, TimeUnit)}.

 */

 

public final class AndroidWatchExecutor implements WatchExecutor {

 

  static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";

 

  private final Handler mainHandler;

  private final Handler backgroundHandler;

  private final long initialDelayMillis;

  private final long maxBackoffFactor;

 

  public AndroidWatchExecutor(long initialDelayMillis) {

    mainHandler = new Handler(Looper.getMainLooper());

    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);

    handlerThread.start();

    backgroundHandler = new Handler(handlerThread.getLooper());

    this.initialDelayMillis = initialDelayMillis;

    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;

  }

 

  @Override public void execute(@NonNull Retryable retryable) {

  //目前線程是否為主線程

    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {

      waitForIdle(retryable, 0);

    } else {

      postWaitForIdle(retryable, 0);

    }

  }

 

  private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {

    mainHandler.post(new Runnable() {

      @Override public void run() {

        waitForIdle(retryable, failedAttempts);

      }

    });

  }

 

  private void waitForIdle(final Retryable retryable, final int failedAttempts) {

    // This needs to be called from the main thread.

//主線程中進行調用,當主線程的消息隊列空閑時調用

    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

      @Override public boolean queueIdle() {

  //轉移到背景線程進行

        postToBackgroundWithDelay(retryable, failedAttempts);

        return false;

      }

    });

  }

 

  private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {

    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);

    long delayMillis = initialDelayMillis * exponentialBackoffFactor;

//延遲一定時間,再執行;

    backgroundHandler.postDelayed(new Runnable() {

      @Override public void run() {

        Retryable.Result result = retryable.run();

        if (result == RETRY) {

          postWaitForIdle(retryable, failedAttempts + 1);

        }

      }

    }, delayMillis);

  }

}
           

AndroidWatchExecutor 是Android平台中記憶體洩漏的監測工具類。該類的主要方法就是execute()方法,其主要邏輯是判斷目前正在執行的線程是否為主線程,如果是主線程,則通過IdleHandler監測主線程是否空閑,當空閑的時候,執行postToBackgroundWithDelay方法,在此方法中,先通過計算exponentialBackoffFactor(補償因子),并通過該因子計算出需要延遲的時間(delayMillis ),說實話為什麼這麼算沒想明白。最後通過backgroundHandler延遲執行一條線程去擷取Retryable.Result的結果,通過是否需要重試也就是“result == RETRY”來判斷是否需要繼續postWaitForIdle方法,同時在重試的時候failedAttempts參數會加1。那麼我們不禁會思考,retryable.run()的結果,也就是Retryable.Result是在什麼時候改變的呢?這個我們在RefWatcher中的ensureGoneAsync()方法的分析中提到,result的值是在ensureGone()方法傳回的。好了就是那麼清晰。當正在執行的線程非主線程的的時候postWaitForIdle()方法被執行,這裡就不再說明了。

下面我們看一下RefWatcher中ensureGone()方法的源碼:

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {

    //GC開始檢測時間

    long gcStartNanoTime = System.nanoTime();

    //從開始watch到GC開始檢測的時間差

    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

 

    //移除已經被GC的引用資訊

    removeWeaklyReachableReferences();

 

    //如果調試模式,則後續重試

    if (debuggerControl.isDebuggerAttached()) {

      // The debugger can create false leaks.

      return RETRY;

    }

 

    //如果不包含應用資訊,則表示監測已經完成,沒有發生洩漏;

    if (gone(reference)) {

      return DONE;

    }

    //觸發GC

    gcTrigger.runGc();

    //再次确認移除已經被GC的引用資訊

    removeWeaklyReachableReferences();

    //如果隊列中還存在引用資訊,

    if (!gone(reference)) {

      long startDumpHeap = System.nanoTime();

      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

 

      //使用Dumper類dump目前堆記憶體中的對象資訊

      File heapDumpFile = heapDumper.dumpHeap();

      if (heapDumpFile == RETRY_LATER) {

        // Could not dump the heap.

        return RETRY;

      }

      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

 

      //将hprof檔案和reference引用資訊構造HeapDump對象

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)

          .referenceName(reference.name)

          .watchDurationMs(watchDurationMs)

          .gcDurationMs(gcDurationMs)

          .heapDumpDurationMs(heapDumpDurationMs)

          .build();

      //分析dump資訊

      heapdumpListener.analyze(heapDump);

    }

    return DONE;

  }
           

要了解LeackCanary是怎麼定義記憶體洩漏的呢?此方法就是關鍵。同時不了解Java中的弱引用,強引用的概念的可以查閱一下這方面的資料,這裡就不在進行過多的解釋了。首先該方法先調用了removeWeaklyReachableReferences()方法,該方法就是移除已經被GC的引用資訊,在該方法裡就是看queue隊列中是否還存在引用的資訊KeyedWeakReference,如果存在就從retainedKeys中進行移除。在RefWatcher類中的watch()方法中已經将觀察對象watchReference對應的key儲存在retainedKeys中,同時關聯到了ReferenceQueue的執行個體對象queue。當弱引用持有的對象被GC之後,與之關聯的引用資訊KeyedWeakReference的對象reference就會被添加到關聯的隊列中queue.是以可以通過queue中是否存在對應的引用資訊來判斷是否發生記憶體洩漏。下面說一下為什麼在開啟Debug模式下需要傳回RETRY,那是因為在該模式下對象引用的時間會變長。我們繼續向下分析,gone(refreence)方法就是看retainedKeys是否包含相應的引用的資訊,如果不存在則說明就完成了GC,本次檢測也告一段落。否則, 通過gcTrigger.runGc()手動觸發一次GC,并在此通過removeWeaklyReachableReferences()方法進行引用資訊的移除。如果還存在該引用資訊那麼說明app目前發生了記憶體洩漏。後續就是通過heapDumper.dumpHeap()将目前堆記憶體中的資訊儲存下來,并通過heapdumpListener.analyze(heapDump)分析擷取的dump資訊。到此,相信大家就清楚了我們Android開發中記憶體洩漏檢測神器LeakCanary的檢測原理了吧。

後續将在下一篇文章中分析該架構是怎麼分析記憶體洩漏的,敬請期待~