LeakCanary源碼解析

LeakCanary源碼解析

前言

對于內(nèi)存泄漏的檢測,基于MAT起點(diǎn)較高,所以一般我們都使用LeakCanary來作為我們的內(nèi)存泄漏檢測工具來使用。

基礎(chǔ)知識

四種引用

LeakCanary主要是基于弱引用來進(jìn)行對于已經(jīng)銷毀的Activity和Fragment的回收監(jiān)控來實(shí)現(xiàn)的。

  • 強(qiáng)引用:無論如何都不會回收。

  • 軟引用:內(nèi)存足夠不回收。內(nèi)存不夠時(shí),就會回收。

  • 弱引用:垃圾回收時(shí)直接回收,則直接回收。

  • 虛引用:垃圾回收時(shí)直接回收。

引用隊(duì)列(ReferenceQueue)。

軟引用和弱引用都可以關(guān)聯(lián)一個(gè)引用隊(duì)列。當(dāng)引用的對象被回收以后,會將軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。LeakCanary的基礎(chǔ)實(shí)現(xiàn)就是將已經(jīng)銷毀的ActivityFragment所對應(yīng)的實(shí)例放入到弱引用中,并關(guān)聯(lián)一個(gè)引用隊(duì)列。如果實(shí)例進(jìn)行了回收,那么弱引用就會放入到ReferenceQueue中,如果一段時(shí)間后,所監(jiān)控的實(shí)例還未在ReferenceQueue中出現(xiàn),那么可以證明出現(xiàn)了內(nèi)存泄漏導(dǎo)致了實(shí)例沒有被回收。

使用方法

配置:

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}

使用:

public class ExampleApplication extends Application {
  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

Leakcanary原理解析

從程序的唯一入口來進(jìn)行分析。本文是基于1.6.3版本來進(jìn)行源碼解析的。對應(yīng)的解析源碼地址為leakcanary-source

注冊實(shí)例的監(jiān)控

    public static @NonNull RefWatcher install(@NonNull Application application) {
        return refWatcher(application)//創(chuàng)建一個(gè)Android端使用的引用監(jiān)控的構(gòu)造者
                .listenerServiceClass(DisplayLeakService.class)
                //設(shè)置不進(jìn)行監(jiān)控的類引用對象
                .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
                //創(chuàng)建對于引用的監(jiān)控
                .buildAndInstall();
    }

這個(gè)方法比較簡短,一個(gè)個(gè)進(jìn)行解析吧。

構(gòu)造一個(gè)AndroidRefWatcherBuilder對象
//創(chuàng)建一個(gè)AndroidRefWatcherBuilder對象   
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {   
    return new AndroidRefWatcherBuilder(context);   
}

這里創(chuàng)建的AndroidRefWatcherBuilder對象是一個(gè)適用于Android端的引用監(jiān)控的構(gòu)造者。

設(shè)置后臺的監(jiān)聽類
  //AndroidRefWatcherBuilder.java
  //設(shè)置一個(gè)類用來監(jiān)聽分析的結(jié)果。
  public @NonNull AndroidRefWatcherBuilder listenerServiceClass(@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
    //設(shè)置一個(gè)監(jiān)聽者
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

  //RefWatcherBuilder.java
  //HeapDump的監(jiān)聽者
  public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
    this.heapDumpListener = heapDumpListener;
    return self();
  }

這里將DisplayLeakService類作為了我們最終內(nèi)存泄漏的分析者,并且該類能夠進(jìn)行內(nèi)存泄漏消息的通知(一般是Notification)。

不納入監(jiān)控的引用

excludedRefs方法能夠?qū)⒁恍┪覀儾魂P(guān)心的引用排除在我們的監(jiān)控范圍以外。這里這么處理,主要是因?yàn)橐恍┫到y(tǒng)級別的引用問題。我們可以具體看一下里面有哪些東西是我們不需要關(guān)注的。

    //由于Android的AOSP本身可能會存在內(nèi)存泄漏的東西,所以對于這些東西默認(rèn)是不會進(jìn)行提醒的。
    public static @NonNull ExcludedRefs.Builder createAppDefaults() {
        //將AndroidExcludedRefs所有的枚舉類型都考慮在內(nèi)。
        return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
    }

    public static @NonNull ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
        ExcludedRefs.Builder excluded = ExcludedRefs.builder();
        //遍歷所有的枚舉類型
        for (AndroidExcludedRefs ref : refs) {
            //如果枚舉類型執(zhí)行引用的排除處理
            if (ref.applies) {
                //調(diào)用枚舉的add方法,這里面會將所有需要排除的引用類都放到出入的excluede中
                ref.add(excluded);
                ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
            }
        }
        return excluded;
    }

這個(gè)可能會有一些難以理解,我們先簡單分析一下AndroidExcludedRefs這個(gè)類。

public enum AndroidExcludedRefs {
    //參數(shù),標(biāo)識是否需要執(zhí)行add方法
    final boolean applies;
    AndroidExcludedRefs() {
        this(true);
    }
    AndroidExcludedRefs(boolean applies) {
        this.applies = applies;
    }
    //枚舉類需要實(shí)現(xiàn)的方法
    abstract void add(ExcludedRefs.Builder excluded);
}

AndroidExcludedRefs是一個(gè)枚舉類型。含有成員變量applies以及add()方法。

我們再分析一個(gè)具體的枚舉類型。

//AndroidExcludedRefs.java
    ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
        @Override
        void add(ExcludedRefs.Builder excluded) {
            //設(shè)置排除的類中的某個(gè)屬性
            excluded.instanceField("android.app.ActivityThread$ActivityClientRecord", "nextIdle")
                    //設(shè)置排除的原因
                    .reason("Android AOSP sometimes keeps a reference to a destroyed activity as a"
                            + " nextIdle client record in the android.app.ActivityThread.mActivities map."
                            + " Not sure what's going on there, input welcome.");
        }
    },

ACTIVITY_CLIENT_RECORD__NEXT_IDLE就是一個(gè)具體的枚舉類型。applies賦值為SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP。也有add方法的具體實(shí)現(xiàn)。實(shí)現(xiàn)中將需要排除的引用類型添加到了excluded中。

所以當(dāng)我們的系統(tǒng)版本號滿足SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP這個(gè)條件的時(shí)候,就會執(zhí)行add方法。

AndroidExcludedRefs具有不同的枚舉實(shí)例,會根據(jù)不同的系統(tǒng)版本來進(jìn)行不同的處理。這里其實(shí)主要是保證對于一些系統(tǒng)級別的內(nèi)存泄漏情況不再進(jìn)行提示。

創(chuàng)建引用的監(jiān)控

我們直接看看buildAndInstall中是如何對已經(jīng)執(zhí)行onDestroy的Activity進(jìn)行監(jiān)控的。

  //根據(jù)對應(yīng)的設(shè)置信息,返回一個(gè)RefWatcher對象
  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //通過構(gòu)造者模式中的build()方法創(chuàng)建一個(gè)RefWatcher對象,這里面會有很多默認(rèn)的設(shè)置
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      //如果允許顯示內(nèi)存泄漏Activity,則進(jìn)行處理
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      //如果設(shè)置了監(jiān)聽Activity,那么就為Activity注冊生命周期監(jiān)聽
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      //如果設(shè)置了監(jiān)聽Fragment,那么就為Fragment注冊生命周期監(jiān)聽
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

我們這里主要看一下如何進(jìn)行Activity以及Fragment的監(jiān)聽的。

  1. 對Activity的處理
    //ActivityRefWatcher.java
    public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
        Application application = (Application) context.getApplicationContext();
        //創(chuàng)建一個(gè)對于Activity的弱引用監(jiān)聽類
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        //對傳入的應(yīng)用的Application注冊一個(gè)對于Activity的生命周期監(jiān)聽函數(shù)
        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
    }

這里創(chuàng)建了一個(gè)ActivityRefWatcher對象,然后將對于應(yīng)用,通過registerActivityLifecycleCallbacks注冊了一個(gè)監(jiān)聽的回調(diào)。

    //ActivityRefWatcher.java
    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
            new ActivityLifecycleCallbacksAdapter() {
                //只監(jiān)聽destory方法,將調(diào)用destory的activity添加到監(jiān)聽watcher中
                @Override
                public void onActivityDestroyed(Activity activity) {
                    refWatcher.watch(activity);
                }
            };

在這個(gè)監(jiān)聽方法中,只監(jiān)聽了Activity的onDestroy方法。當(dāng)Activity銷毀的時(shí)候,使用refWatcher來監(jiān)控其實(shí)例。

  1. 對Fragment的處理
//FragmentRefWatcher.java
    public static void install(Context context, RefWatcher refWatcher) {
            List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
            //將實(shí)現(xiàn)了FragmentRefWatcher接口的兩個(gè)實(shí)現(xiàn)類加入到fragmentRefWatchers中
            //兩個(gè)實(shí)現(xiàn)類,一個(gè)是實(shí)現(xiàn)對于V4包下的Fragment的監(jiān)聽,一個(gè)是對于當(dāng)前包下Fragment的監(jiān)聽
            if (SDK_INT >= O) {
                //實(shí)現(xiàn)類AndroidOFragmentRefWatcher
                fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
            }

            try {
                //實(shí)現(xiàn)類SupportFragmentRefWatcher用于監(jiān)聽V4包下面的Fragment
                //這里使用反射,是因?yàn)镾upportFragmentRefWatcher這個(gè)類在support-fragment這個(gè)module中。
                //所以,如果我們沒有引入V4的話,其實(shí)這個(gè)類是可以不引入的。
                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) {
            }
            //如果沒有Fragment的監(jiān)控者,那么直接返回
            if (fragmentRefWatchers.size() == 0) {
                return;
            }
            //創(chuàng)建Helper實(shí)例
            Helper helper = new Helper(fragmentRefWatchers);
            Application application = (Application) context.getApplicationContext();
            //注冊Activity的生命周期回調(diào)
            application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
        }

由于我們經(jīng)常使用的Fragment包含兩種,一種是support包中的Fragment,一種是標(biāo)準(zhǔn)的app包中的Fragment。這里對這兩種都進(jìn)行了處理。

我們看一下對于注冊的生命周期函數(shù)

        private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
                new ActivityLifecycleCallbacksAdapter() {
                    @Override
                    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                        for (FragmentRefWatcher watcher : fragmentRefWatchers) {
                            //這里會調(diào)用具體的實(shí)現(xiàn)類的watchFragments方法。這里關(guān)心的是綁定的Activity的onCreate方法。走到這里的時(shí)候已經(jīng)創(chuàng)建了對應(yīng)FragmentManager對象
                            //而通過FragmentManager對象可以來registerFragmentLifecycleCallbacks來創(chuàng)建對于其管理的Fragment的生命周期監(jiān)聽
                            watcher.watchFragments(activity);
                        }
                    }
                };

這里我們同樣是注冊了Activity的生命周期回調(diào)。但是這里監(jiān)控的是onActivityCreated方法。我們這里看一下watchFragments的實(shí)現(xiàn)。

具體的實(shí)現(xiàn)有兩個(gè)類,一個(gè)是SupportFragmentRefWatcher,一個(gè)是AndroidOFragmentRefWatcher。我們這里只分析第一個(gè)。剩下的另一個(gè)是類似的,只是因?yàn)槭褂玫腇ragment不同,而有所區(qū)別。

    public void watchFragments(Activity activity) {
        //V4包中的Fragment,必須使用FragmentActivity來進(jìn)行處理
        if (activity instanceof FragmentActivity) {
            FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
            supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
        }
    }

    private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
            new FragmentManager.FragmentLifecycleCallbacks() {

                @Override
                public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
                    View view = fragment.getView();
                    if (view != null) {
                        //當(dāng)fragment的view銷毀的時(shí)候,開始監(jiān)控
                        refWatcher.watch(view);
                    }
                }

                @Override
                public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
                    //當(dāng)fragment銷毀的時(shí)候,開始監(jiān)控
                    refWatcher.watch(fragment);
                }
            };

所以,這里通過獲取Activity中的FragmentManager,通過registerFragmentLifecycleCallbacks來對于其管理的Fragment的生命周期進(jìn)行監(jiān)聽。當(dāng)Fragment執(zhí)行銷毀的時(shí)候,將其引用加入到監(jiān)控隊(duì)列。

到這里為止,就已經(jīng)將我們的Activity和Fragment通過refWatcherwatch進(jìn)行了監(jiān)控。

那么我們下一步分析,watch方法中又是如何監(jiān)控實(shí)例,并判斷其存在內(nèi)存泄漏的。

監(jiān)控

我們對于已經(jīng)銷毀的界面會通過refWatcherwatch方法來進(jìn)行監(jiān)控。

//RefWatcher.java
    public void watch(Object watchedReference) {
        //重載方法
        watch(watchedReference, "");
    }
    public void watch(Object watchedReference, String referenceName) {
        if (this == DISABLED) {
            return;
        }
        //保證watch的對象不為空
        checkNotNull(watchedReference, "watchedReference");
        checkNotNull(referenceName, "referenceName");
        final long watchStartNanoTime = System.nanoTime();
        //創(chuàng)建一個(gè)UUID
        String key = UUID.randomUUID().toString();
        //將UUID保存到set中
        retainedKeys.add(key);
        //創(chuàng)建一個(gè)弱引用,指向要檢測的對象。
        //如果這個(gè)弱引用被回收,那么會將reference加入到queue隊(duì)列中
        
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
        //判斷reference是否被回收
        ensureGoneAsync(watchStartNanoTime, reference);
    }

這個(gè)里面主要執(zhí)行了3個(gè)操作

  • 創(chuàng)建了UUID
  • 將生成的UUID保存到retainedKeys隊(duì)列中。
  • 創(chuàng)建一個(gè)弱引用,指定了對應(yīng)的引用隊(duì)列queue

這里的retainedKeys隊(duì)列記錄了我們執(zhí)行了監(jiān)控的引用對象。而queue中會保存回收的引用。所以通過二者的對比,我們就可以找到內(nèi)存泄漏的引用了。

我們看一下ensureGoneAsync中是如何執(zhí)行這個(gè)操作過程的。

    //RefWatcher.java
    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        watchExecutor.execute(new Retryable() {
            @Override
            public Retryable.Result run() {
                return ensureGone(reference, watchStartNanoTime);
            }
        });
    }

這里的watcheExecute使用的是AndroidWatchExecutor

//AndroidRefWatcherBuilder.java
  @Override protected @NonNull WatchExecutor defaultWatchExecutor() {
    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
  }

我們跟蹤一下execute方法。

//AndroidWatchExecutor.java
  @Override public void execute(@NonNull Retryable retryable) {
    //如果當(dāng)前線程是主線程,則直接執(zhí)行waitForIdl
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      //如果不是主線程,則通過Handler機(jī)制,將waitForIdle放入到主線程去執(zhí)行
      postWaitForIdle(retryable, 0);
    }
  }

  private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
    //通過Handler機(jī)制,將waitForIdle發(fā)送到主線程執(zhí)行
    mainHandler.post(new Runnable() {
      @Override public void run() {
        waitForIdle(retryable, failedAttempts);
      }
    });
  }

  private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    //當(dāng)messagequeue閑置時(shí),增加一個(gè)處理。這種方法主要是為了提升性能,不會影響我們正常的應(yīng)用流暢度
    //這個(gè)方法會在主線程執(zhí)行,所以postToBackgroundWithDelay會在主線程執(zhí)行
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

所以這里最終都會在主線程中執(zhí)行postToBackgroundWithDelay方法。

  private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    //計(jì)算補(bǔ)償因子。如果返回了重試的話,這個(gè)failedAttempts回增加,會使得方法的執(zhí)行時(shí)間延遲時(shí)間增加。
    //比如說第一次,演示5秒執(zhí)行,但是執(zhí)行結(jié)果為RETRY,那么下一次就是延遲10秒來執(zhí)行了
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    //計(jì)算延遲時(shí)間
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    //backgroundHandler會將run方法中的代碼放在一個(gè)新的線程中去執(zhí)行。
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

這個(gè)方法的執(zhí)行,會根據(jù)執(zhí)行的次數(shù)進(jìn)行來延遲執(zhí)行對應(yīng)的run方法。

我們看一下retryable.run()方法的執(zhí)行。也就回到了我們的RefWatcher中的ensureGoneAsync方法。

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}

這里的ensureGone方法屬于我們最核心的代碼了。

    //判斷reference是否被回收
    @SuppressWarnings("ReferenceEquality")
    // Explicitly checking for named null.
    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        //移除已經(jīng)回收的監(jiān)控對象
        removeWeaklyReachableReferences();
        //如果當(dāng)前是debug狀態(tài),則直接返回retry
        if (debuggerControl.isDebuggerAttached()) {
            // The debugger can create false leaks.
            return RETRY;
        }
        //監(jiān)控對象已經(jīng)回收了,直接返回Done
        if (gone(reference)) {
            return DONE;
        }
        //執(zhí)行一次垃圾回收
        gcTrigger.runGc();
        //再次移除已經(jīng)回收的監(jiān)控對象
        removeWeaklyReachableReferences();
        if (!gone(reference)) {
            //如果仍然沒有回收,證明發(fā)生了內(nèi)存泄漏
            long startDumpHeap = System.nanoTime();
            //gc執(zhí)行的時(shí)長
            long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
            //dump出hprof文件
            File heapDumpFile = heapDumper.dumpHeap();
            if (heapDumpFile == RETRY_LATER) {
                // Could not dump the heap.
                //不能生成快照文件的話,進(jìn)行重試
                return RETRY;
            }
            //生成hprof文件消耗的的時(shí)間
            long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
            HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
                    .referenceName(reference.name)
                    .watchDurationMs(watchDurationMs)
                    .gcDurationMs(gcDurationMs)
                    .heapDumpDurationMs(heapDumpDurationMs)
                    .build();
            //分析堆內(nèi)存,heapdumpListener默認(rèn)是ServiceHeapDumpListener
            heapdumpListener.analyze(heapDump);
        }
        return DONE;
    }

這段代碼執(zhí)行了幾個(gè)過程

  1. 移除已經(jīng)回收的監(jiān)控對象
  2. 如果當(dāng)前監(jiān)控的對象已經(jīng)回收了,直接返回DONE。
  3. 如果沒有回收,則強(qiáng)行執(zhí)行一次GC操作。
  4. 再次移除已經(jīng)回收的監(jiān)控對象。
  5. 如果當(dāng)前監(jiān)控對象仍然沒有回收,則dump出hprof文件,然后根據(jù)快照文件進(jìn)行內(nèi)存泄漏情況的分析。

這里我們對每個(gè)方法都一一的進(jìn)行一次分析

移除已回收的弱引用對象
    private void removeWeaklyReachableReferences() {
        KeyedWeakReference ref;
        //循環(huán)queue
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
            //在queue中的ref,說明已經(jīng)被回收了,所以直接將其對應(yīng)的key從retainedKeys移除。
            retainedKeys.remove(ref.key);
        }
    }

這里的queue是我們提到的引用隊(duì)列,而retainedKeys中則保存著我們要監(jiān)控的對象。當(dāng)對象被回收以后,就會將對應(yīng)的弱引用信息保存到queue中,所以我們將queue中的相關(guān)弱引用信息從retainedKeys移除。剩下的就是我們在監(jiān)聽或者已經(jīng)發(fā)生內(nèi)存泄漏的對象了。

判斷監(jiān)控對象是否回收
    //判斷監(jiān)控的對象是否已經(jīng)回收 true:已經(jīng)回收
    private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
    }

在上一步中,我們已經(jīng)將回收的引用信息從retainedKeys中移除了,所以這里只要通過判斷這個(gè)set中是否有我們監(jiān)控的這個(gè)類即可。

導(dǎo)出.hprof文件
  public File dumpHeap() {
    //創(chuàng)建一個(gè).hrof文件
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
    if (heapDumpFile == RETRY_LATER) {
      //創(chuàng)建失敗了,等會再重試
      return RETRY_LATER;
    }
    FutureResult<Toast> waitingForToast = new FutureResult<>();
    //通過Handler機(jī)制在主線程顯示Toast,使用了CountDownLatch機(jī)制。顯示Toast的時(shí)候會將其數(shù)值修改為0,
    showToast(waitingForToast);
    //這里會等待主線程顯示Toast,也就是CountDownLatch變?yōu)?。然后就可以繼續(xù)后面的操作
    if (!waitingForToast.wait(5, SECONDS)) {
      CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
      return RETRY_LATER;
    }
    //創(chuàng)建一個(gè)Notification通知
    Notification.Builder builder = new Notification.Builder(context)
        .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
    Notification notification = LeakCanaryInternals.buildNotification(context, builder);
    NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    int notificationId = (int) SystemClock.uptimeMillis();
    notificationManager.notify(notificationId, notification);

    Toast toast = waitingForToast.get();
    try {
      //創(chuàng)建heap堆的快照信息,可以獲知程序的哪些部分正在使用大部分的內(nèi)存
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      //關(guān)閉Toask和Notification通知
      cancelToast(toast);
      notificationManager.cancel(notificationId);
      return heapDumpFile;
    } catch (Exception e) {
      CanaryLog.d(e, "Could not dump heap");
      // Abort heap dump
      return RETRY_LATER;
    }
  }

這里會創(chuàng)建一個(gè).hprof文件,然后顯示一個(gè)Toast和Notification通知,再將內(nèi)存泄漏時(shí)候的堆的快照信息保存的.hprof文件中,最后將Toast和Notification通知關(guān)閉。所以執(zhí)行完這個(gè)操作之后,我們生成的.hprof文件中就保存了對應(yīng)的內(nèi)存泄漏時(shí)的堆的相關(guān)信息了。

快照文件分析

當(dāng)生成了文件以后,會通過heapdumpListener來分析生成的快照文件。這里的listener默認(rèn)的是ServiceHeapDumpListener類

  //AndroidRefWatcherBuilder.java
  @Override protected @NonNull HeapDump.Listener defaultHeapDumpListener() {
    return new ServiceHeapDumpListener(context, DisplayLeakService.class);
  }

我們看一下它的analyze方法

  //ServiceHeapDumpListener.java
   public void analyze(@NonNull HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
  //HeapAnalyzerService.java
  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    setEnabledBlocking(context, HeapAnalyzerService.class, true);
    setEnabledBlocking(context, listenerServiceClass, true);
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    //這里的listenerServiceClass是DisplayLeakService
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    //啟動一個(gè)前臺的服務(wù),啟動時(shí),會調(diào)用onHandleIntent方法,該方法在父類中實(shí)現(xiàn)了。實(shí)現(xiàn)中會調(diào)用onHandleIntentInForeground()方法
    ContextCompat.startForegroundService(context, intent);
  }

這里啟動了一個(gè)服務(wù)來進(jìn)行對于文件的分析功能。當(dāng)啟動服務(wù)的時(shí)候會調(diào)用onHandleIntent方法。HeapAnalyzerServiceonHandleIntent是在其父類中實(shí)現(xiàn)的。

//ForegroundService.java
@Override protected void onHandleIntent(@Nullable Intent intent) {
  onHandleIntentInForeground(intent);
}

所以會調(diào)用onHandleIntentInForeground這個(gè)方法。

    protected void onHandleIntentInForeground(@Nullable Intent intent) {
        String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
        HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
        //創(chuàng)建一個(gè)堆分析器
        HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
        //**重點(diǎn)分析方法***分析內(nèi)存泄漏結(jié)果
        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
        //調(diào)用接口,將結(jié)果回調(diào)給listenerClassName所對應(yīng)的類(這里是DisplayLeakService類)來進(jìn)行處理
        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
    }

這里會創(chuàng)建一個(gè)堆分析器,對于我們的快照文件進(jìn)行分析,然后將結(jié)果通過AbstractAnalysisResultService的方法,將結(jié)果交給DisplayLeakService類來進(jìn)行處理。

檢測泄漏結(jié)果

HeapAnalyzer類的作用主要就是通過對.hprof文件的分析,檢測我們監(jiān)控的對象是否發(fā)生了內(nèi)存的泄漏

//HeapAnalyzer.java
//將hprof文件解析,解析為對應(yīng)的AnalysisResult對象
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile, @NonNull String referenceKey, boolean computeRetainedSize) {
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {
        Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
        return failure(exception, since(analysisStartNanoTime));
    }

    try {
        //開始讀取Dump文件
        listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
        HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
        //.hprof的解析器,這個(gè)是haha庫的類
        HprofParser parser = new HprofParser(buffer);
        listener.onProgressUpdate(PARSING_HEAP_DUMP);
        //解析生成快照,快照中會包含所有被引用的對象信息
        Snapshot snapshot = parser.parse();
        listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
        deduplicateGcRoots(snapshot);
        listener.onProgressUpdate(FINDING_LEAKING_REF);
        //根據(jù)key值,查找快照中是否有所需要的對象
        Instance leakingRef = findLeakingReference(referenceKey, snapshot);
        if (leakingRef == null) {
            //表示對象不存在,在gc的時(shí)候,進(jìn)行了回收。表示沒有內(nèi)存泄漏
            String className = leakingRef.getClassObj().getClassName();
            return noLeak(className, since(analysisStartNanoTime));
        }
        //檢測泄漏的路徑,并將檢測的結(jié)果進(jìn)行返回
        return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
        return failure(e, since(analysisStartNanoTime));
    }
}

這個(gè)方法使用了haha三方類庫來對.hprof文件解析以及處理。里面的主要流程如下:

  1. 創(chuàng)建一個(gè).hprof文件的buffer來進(jìn)行文件的讀取
  2. 通過HprofParser解析器來解析hprof文件,生成Snapshot對象。在這一步中構(gòu)建了一顆對象的引用關(guān)系樹,我們可以在這顆樹中查詢各個(gè)Object的信息,包括Class信息、內(nèi)存地址、持有的引用以及被持有引用的關(guān)系。
  3. 根據(jù)傳入的監(jiān)控的對象key值,獲取其在Snapshot中所對應(yīng)的引用leakingRef。
  4. 分析leakingRef,獲取到內(nèi)存泄漏的路徑。這里會找到一條到泄漏對象的最短引用路徑。這個(gè)過程由findLeakTrace來完成,實(shí)際上尋找最短引用路徑的邏輯是封裝在PathsFromGCRootsComputerImpl類的getNextShortestPath和processCurrentReferrefs方法中
泄漏的通知

當(dāng)找到我們的內(nèi)存泄漏的路徑后,會調(diào)用AbstractAnalysisResultService.sendResultToListener將結(jié)果交給DisplayLeakService類來進(jìn)行處理。

//AbstractAnalysisResultService.java
public static void sendResultToListener(@NonNull Context context,
    @NonNull String listenerServiceClassName,
    @NonNull HeapDump heapDump,
    @NonNull AnalysisResult result) {
  Class<?> listenerServiceClass;
  try {
    //通過反射獲取到一個(gè)類信息
    listenerServiceClass = Class.forName(listenerServiceClassName);
  } catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
  }
  Intent intent = new Intent(context, listenerServiceClass);
  //將結(jié)果保存到文件中,然后將文件路徑傳遞給service
  File analyzedHeapFile = AnalyzedHeap.save(heapDump, result);
  if (analyzedHeapFile != null) {
    intent.putExtra(ANALYZED_HEAP_PATH_EXTRA, analyzedHeapFile.getAbsolutePath());
  }
  //啟動服務(wù),然后傳遞內(nèi)存泄漏分析的結(jié)果文件所對應(yīng)的位置
  ContextCompat.startForegroundService(context, intent);
}

這里會啟動一個(gè)DisplayLeakService服務(wù),傳遞了對應(yīng)的內(nèi)存泄漏分析結(jié)果的文件路徑信息。

然后通過onHandleIntent()->onHandleIntentInForeground()->onHeapAnalyzed()。最終調(diào)用了DisplayLeakServiceonHeapAnalyzed方法

protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {
    HeapDump heapDump = analyzedHeap.heapDump;
    AnalysisResult result = analyzedHeap.result;
    //根據(jù)泄漏的信息,生成提示的String字符串
    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", leakInfo);
    //重命名.hprof文件
    heapDump = renameHeapdump(heapDump);
    //保存分析的結(jié)果
    boolean resultSaved = saveResult(heapDump, result);
    //結(jié)果表頭
    String contentTitle;
    if (resultSaved) {
        PendingIntent pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
        if (result.failure != null) {
            //分析失敗
            contentTitle = getString(R.string.leak_canary_analysis_failed);
        } else {
            String className = classSimpleName(result.className);
            if (result.leakFound) {//檢測到內(nèi)存泄漏
                if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
                    if (result.excludedLeak) {//被排除的檢測結(jié)果
                        contentTitle = getString(R.string.leak_canary_leak_excluded, className);
                    } else {
                        contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
                    }
                } else {
                    String size = formatShortFileSize(this, result.retainedHeapSize);
                    if (result.excludedLeak) {
                        contentTitle = getString(R.string.leak_canary_leak_excluded_retaining, className, size);
                    } else {
                        contentTitle = getString(R.string.leak_canary_class_has_leaked_retaining, className, size);
                    }
                }
            } else {
                //未檢測到內(nèi)存泄漏
                contentTitle = getString(R.string.leak_canary_class_no_leak, className);
            }
        }
        String contentText = getString(R.string.leak_canary_notification_message);
        //***重點(diǎn)方法***顯示一個(gè)Notification通知
        showNotification(pendingIntent, contentTitle, contentText);
    } else {
        onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));
    }
    //鉤子函數(shù),可以重寫此方法,將內(nèi)存的泄露信息和對應(yīng)的.hprof文件上傳到服務(wù)器。
    // 需要注意,leakfind和excludedLeak的情況都會調(diào)用這個(gè)方法
    afterDefaultHandling(heapDump, result, leakInfo);
}

這個(gè)服務(wù)的作用就是將我們分析之后的泄漏路徑的相關(guān)信息通過Notification的通知形式,告知用戶具體的內(nèi)存泄漏情況。

在程序的最后有一個(gè)afterDefaultHandling方法,這個(gè)方法是一個(gè)空實(shí)現(xiàn),用戶可以覆寫這個(gè)方法來實(shí)現(xiàn)將內(nèi)存泄漏的信息上傳到服務(wù)器的功能

到這里為止LeakCanary的整個(gè)實(shí)現(xiàn)流程解析完成了。

學(xué)習(xí)到的新知識

整篇的學(xué)習(xí),還是學(xué)到了一些之前沒有認(rèn)識到的東西的。

  1. 主要是通過registerActivityLifecycleCallbacks來注冊對于我們銷毀的Activity的監(jiān)聽。
  2. 使用了弱引用的引用隊(duì)列方式對于我們已經(jīng)銷毀的Activity的引用信息進(jìn)行監(jiān)控,檢測其是否被回收。
  3. 對于執(zhí)行垃圾回收需要使用Runtime.getRuntime().gc()
  4. 可以使用CountDownLatch來實(shí)現(xiàn)線程之間的同步處理。比如說這套源碼里面對于showToast的處理。
  5. 不同的Android版本本身可能就存在一些內(nèi)存泄漏的情況。
  6. LeakCanary可以通過覆寫afterDefaultHandling方法來實(shí)現(xiàn)對于內(nèi)存泄漏信息的自行處理

源碼解析項(xiàng)目地址:leakcanary-source

本文由 開了肯 發(fā)布!

同步公眾號[開了肯]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評論 2 380