LeakCanary源碼解析

LeakCanary源碼解析

前言

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

基礎(chǔ)知識

四種引用

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

  • 強引用:無論如何都不會回收。

  • 軟引用:內(nèi)存足夠不回收诱篷。內(nèi)存不夠時,就會回收雳灵。

  • 弱引用:垃圾回收時直接回收棕所,則直接回收。

  • 虛引用:垃圾回收時直接回收悯辙。

引用隊列(ReferenceQueue)琳省。

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

使用方法

配置:

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原理解析

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

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

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

這個方法比較簡短,一個個進行解析吧脉幢。

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

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

設(shè)置后臺的監(jiān)聽類
  //AndroidRefWatcherBuilder.java
  //設(shè)置一個類用來監(jiān)聽分析的結(jié)果。
  public @NonNull AndroidRefWatcherBuilder listenerServiceClass(@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
    //設(shè)置一個監(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)存泄漏的分析者鸵隧,并且該類能夠進行內(nèi)存泄漏消息的通知(一般是Notification)绸罗。

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

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

    //由于Android的AOSP本身可能會存在內(nèi)存泄漏的東西外驱,所以對于這些東西默認是不會進行提醒的育灸。
    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;
    }

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

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

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

我們再分析一個具體的枚舉類型蒋譬。

//AndroidExcludedRefs.java
    ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
        @Override
        void add(ExcludedRefs.Builder excluded) {
            //設(shè)置排除的類中的某個屬性
            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就是一個具體的枚舉類型割岛。applies賦值為SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP。也有add方法的具體實現(xiàn)犯助。實現(xiàn)中將需要排除的引用類型添加到了excluded中癣漆。

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

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

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

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

  //根據(jù)對應(yīng)的設(shè)置信息,返回一個RefWatcher對象
  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //通過構(gòu)造者模式中的build()方法創(chuàng)建一個RefWatcher對象,這里面會有很多默認的設(shè)置
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      //如果允許顯示內(nèi)存泄漏Activity倒槐,則進行處理
      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;
  }

我們這里主要看一下如何進行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)建一個對于Activity的弱引用監(jiān)聽類
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        //對傳入的應(yīng)用的Application注冊一個對于Activity的生命周期監(jiān)聽函數(shù)
        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
    }

這里創(chuàng)建了一個ActivityRefWatcher對象两残,然后將對于應(yīng)用,通過registerActivityLifecycleCallbacks注冊了一個監(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);
                }
            };

在這個監(jiān)聽方法中,只監(jiān)聽了Activity的onDestroy方法着逐。當Activity銷毀的時候崔赌,使用refWatcher來監(jiān)控其實例。

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

            try {
                //實現(xiàn)類SupportFragmentRefWatcher用于監(jiān)聽V4包下面的Fragment
                //這里使用反射,是因為SupportFragmentRefWatcher這個類在support-fragment這個module中秀姐。
                //所以慈迈,如果我們沒有引入V4的話,其實這個類是可以不引入的省有。
                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實例
            Helper helper = new Helper(fragmentRefWatchers);
            Application application = (Application) context.getApplicationContext();
            //注冊Activity的生命周期回調(diào)
            application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
        }

由于我們經(jīng)常使用的Fragment包含兩種,一種是support包中的Fragment蠢沿,一種是標準的app包中的Fragment伸头。這里對這兩種都進行了處理。

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

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

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

具體的實現(xiàn)有兩個類,一個是SupportFragmentRefWatcher速缨,一個是AndroidOFragmentRefWatcher锌妻。我們這里只分析第一個。剩下的另一個是類似的旬牲,只是因為使用的Fragment不同仿粹,而有所區(qū)別。

    public void watchFragments(Activity activity) {
        //V4包中的Fragment原茅,必須使用FragmentActivity來進行處理
        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) {
                        //當fragment的view銷毀的時候吭历,開始監(jiān)控
                        refWatcher.watch(view);
                    }
                }

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

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

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

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

監(jiān)控

我們對于已經(jīng)銷毀的界面會通過refWatcherwatch方法來進行監(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)建一個UUID
        String key = UUID.randomUUID().toString();
        //將UUID保存到set中
        retainedKeys.add(key);
        //創(chuàng)建一個弱引用睬罗,指向要檢測的對象。
        //如果這個弱引用被回收旭斥,那么會將reference加入到queue隊列中
        
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
        //判斷reference是否被回收
        ensureGoneAsync(watchStartNanoTime, reference);
    }

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

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

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

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

    //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) {
    //如果當前線程是主線程也祠,則直接執(zhí)行waitForIdl
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      //如果不是主線程,則通過Handler機制近速,將waitForIdle放入到主線程去執(zhí)行
      postWaitForIdle(retryable, 0);
    }
  }

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

  private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    //當messagequeue閑置時,增加一個處理削葱。這種方法主要是為了提升性能奖亚,不會影響我們正常的應(yīng)用流暢度
    //這個方法會在主線程執(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) {
    //計算補償因子昔字。如果返回了重試的話,這個failedAttempts回增加首繁,會使得方法的執(zhí)行時間延遲時間增加作郭。
    //比如說第一次,演示5秒執(zhí)行弦疮,但是執(zhí)行結(jié)果為RETRY夹攒,那么下一次就是延遲10秒來執(zhí)行了
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    //計算延遲時間
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    //backgroundHandler會將run方法中的代碼放在一個新的線程中去執(zhí)行。
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

這個方法的執(zhí)行胁塞,會根據(jù)執(zhí)行的次數(shù)進行來延遲執(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();
        //如果當前是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í)行的時長
            long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
            //dump出hprof文件
            File heapDumpFile = heapDumper.dumpHeap();
            if (heapDumpFile == RETRY_LATER) {
                // Could not dump the heap.
                //不能生成快照文件的話斥季,進行重試
                return RETRY;
            }
            //生成hprof文件消耗的的時間
            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默認是ServiceHeapDumpListener
            heapdumpListener.analyze(heapDump);
        }
        return DONE;
    }

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

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

這里我們對每個方法都一一的進行一次分析

移除已回收的弱引用對象
    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是我們提到的引用隊列赎败,而retainedKeys中則保存著我們要監(jiān)控的對象秕衙。當對象被回收以后,就會將對應(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中移除了勇吊,所以這里只要通過判斷這個set中是否有我們監(jiān)控的這個類即可。

導(dǎo)出.hprof文件
  public File dumpHeap() {
    //創(chuàng)建一個.hrof文件
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
    if (heapDumpFile == RETRY_LATER) {
      //創(chuàng)建失敗了窍仰,等會再重試
      return RETRY_LATER;
    }
    FutureResult<Toast> waitingForToast = new FutureResult<>();
    //通過Handler機制在主線程顯示Toast汉规,使用了CountDownLatch機制。顯示Toast的時候會將其數(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)建一個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)建一個.hprof文件悟民,然后顯示一個Toast和Notification通知,再將內(nèi)存泄漏時候的堆的快照信息保存的.hprof文件中篷就,最后將Toast和Notification通知關(guān)閉射亏。所以執(zhí)行完這個操作之后近忙,我們生成的.hprof文件中就保存了對應(yīng)的內(nèi)存泄漏時的堆的相關(guān)信息了。

快照文件分析

當生成了文件以后智润,會通過heapdumpListener來分析生成的快照文件及舍。這里的listener默認的是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);
    //啟動一個前臺的服務(wù),啟動時窟绷,會調(diào)用onHandleIntent方法锯玛,該方法在父類中實現(xiàn)了。實現(xiàn)中會調(diào)用onHandleIntentInForeground()方法
    ContextCompat.startForegroundService(context, intent);
  }

這里啟動了一個服務(wù)來進行對于文件的分析功能兼蜈。當啟動服務(wù)的時候會調(diào)用onHandleIntent方法攘残。HeapAnalyzerServiceonHandleIntent是在其父類中實現(xiàn)的。

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

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

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

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

檢測泄漏結(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的解析器,這個是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的時候,進行了回收秘蛇。表示沒有內(nèi)存泄漏
            String className = leakingRef.getClassObj().getClassName();
            return noLeak(className, since(analysisStartNanoTime));
        }
        //檢測泄漏的路徑其做,并將檢測的結(jié)果進行返回
        return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
        return failure(e, since(analysisStartNanoTime));
    }
}

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

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

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

//AbstractAnalysisResultService.java
public static void sendResultToListener(@NonNull Context context,
    @NonNull String listenerServiceClassName,
    @NonNull HeapDump heapDump,
    @NonNull AnalysisResult result) {
  Class<?> listenerServiceClass;
  try {
    //通過反射獲取到一個類信息
    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);
}

這里會啟動一個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);
        //***重點方法***顯示一個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)用這個方法
    afterDefaultHandling(heapDump, result, leakInfo);
}

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

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

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

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

整篇的學(xué)習(xí)窑眯,還是學(xué)到了一些之前沒有認識到的東西的屏积。

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

源碼解析項目地址:leakcanary-source

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

同步公眾號[開了肯]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奕枝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓶堕,更是在濱河造成了極大的恐慌隘道,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郎笆,死亡現(xiàn)場離奇詭異谭梗,居然都是意外死亡,警方通過查閱死者的電腦和手機宛蚓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門激捏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凄吏,你說我怎么就攤上這事远舅∫贾茫” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵表谊,是天一觀的道長钞护。 經(jīng)常有香客問我,道長爆办,這世上最難降的妖魔是什么难咕? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮距辆,結(jié)果婚禮上余佃,老公的妹妹穿的比我還像新娘。我一直安慰自己跨算,他們只是感情好爆土,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诸蚕,像睡著了一般步势。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上背犯,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天坏瘩,我揣著相機與錄音,去河邊找鬼漠魏。 笑死倔矾,一個胖子當著我的面吹牛莺琳,可吹牛的內(nèi)容都是我干的稽坤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼爆价,長吁一口氣:“原來是場噩夢啊……” “哼禁熏!你這毒婦竟也來了壤巷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤匹层,失蹤者是張志新(化名)和其女友劉穎隙笆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體升筏,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡撑柔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了您访。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铅忿。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖灵汪,靈堂內(nèi)的尸體忽然破棺而出檀训,到底是詐尸還是另有隱情柑潦,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布峻凫,位于F島的核電站渗鬼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏荧琼。R本人自食惡果不足惜譬胎,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望命锄。 院中可真熱鬧堰乔,春花似錦、人聲如沸脐恩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驶冒。三九已至苟翻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間只怎,已是汗流浹背袜瞬。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留身堡,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓拍鲤,卻偏偏與公主長得像贴谎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子季稳,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355