LeakCanary源碼解析-很值得我們學(xué)習(xí)的一款框架

前言

一直在使用這個(gè)框架,但是一直沒有注意這個(gè)框架的實(shí)現(xiàn)原理。使用過這款框架的人應(yīng)該都知道,LeakCanary是一款能夠幫助開發(fā)者檢查內(nèi)存泄漏的開源庫,只需要簡單配置,就可以當(dāng)使用過程中產(chǎn)生內(nèi)存泄漏時(shí),彈出通知,并且可以我們可以查看詳細(xì)的引用鏈,幫助我們進(jìn)行內(nèi)存泄漏的分析。

項(xiàng)目預(yù)覽

項(xiàng)目結(jié)構(gòu)

這里專門放了一張LeakCanary的項(xiàng)目的目錄結(jié)構(gòu),可以看到,LeakCanary的項(xiàng)目目錄還是很考究的,總體分為了5個(gè)模塊,每個(gè)都專門負(fù)責(zé)一個(gè)功能。

1.analyzer 內(nèi)存泄漏分析功能模塊,內(nèi)存泄漏的路徑分析就是該模塊,haha庫就是在該模塊中使用
2.watcher 內(nèi)存泄漏監(jiān)控模塊,監(jiān)控并且發(fā)現(xiàn)內(nèi)存泄漏的就是該模塊
3.android 將內(nèi)存泄漏監(jiān)控和Android的生命周期綁定,實(shí)現(xiàn)Android中的內(nèi)存泄漏監(jiān)聽
4.android-no-op 空殼模塊,實(shí)現(xiàn)真正發(fā)布realeas包后,不進(jìn)行內(nèi)存泄漏監(jiān)控,損耗性能
5.sample demo模塊

從上面的分析可以看出,LeakCanary這款內(nèi)存泄漏框架的目錄結(jié)構(gòu)真的很考究,按照LeakCanary的功能體積,完全沒必要拆分的這么散,但是這樣做,充分將LeakCanary的功能發(fā)揮到最大化,為什么這么說哪?
熟悉LeakCanary原理的都知道,LeankCanary其實(shí)是利用Java的弱引用特性,加上JVM回收一定的機(jī)制,實(shí)現(xiàn)內(nèi)存泄漏的監(jiān)控的,也就是說,LeakCanary并不是一款和Android死綁定的一款開源框架,所以這時(shí)候LeakCanary這樣的目錄結(jié)構(gòu)就體現(xiàn)出了它的優(yōu)點(diǎn),我們完全可以將watcher模塊或者analyzer模塊拆分出來,處理其他以Java語言作為開發(fā)語言的項(xiàng)目的內(nèi)存泄漏分析。

源碼分析

還是老方式,一個(gè)好的框架肯定有一個(gè)好的外觀類來統(tǒng)領(lǐng)入口,LeakCanary當(dāng)然不能少,一般我們的使用方式就是:

protected void setupLeakCanary() {
    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);
  }

當(dāng)然最核心的就是這句代碼LeakCanary.install(this);前面的LeakCanary.isInAnalyzerProcess(this)這里先暫不分析(當(dāng)然后面會(huì)分析的,這里面有一個(gè)很棒的方法)

public static RefWatcher install(Application application) {
    //創(chuàng)建RefWatcher
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
            //設(shè)置已知的內(nèi)存泄漏問題,或者系統(tǒng)的內(nèi)存泄漏問題
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

這里可以看到,總體上來看分為了四個(gè)步驟:

  1. 創(chuàng)建了RefWatcher(也就是檢測內(nèi)存泄漏的類)
  2. 設(shè)置了內(nèi)存泄漏的通知Service(通知)
  3. 設(shè)置了需要忽略的已知的系統(tǒng)級(jí)別的內(nèi)存泄漏(可以自定義)
  4. 開始監(jiān)聽

接下來就分別看看每一個(gè)步驟。

第一個(gè)步驟

/**
   * 創(chuàng)建一個(gè)builder對(duì)象
   */
  public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

第一個(gè)步驟沒什么好說的,創(chuàng)建了一個(gè)專門為Android使用的Watcher的Builder類。后面我們看的很多方法都基于這個(gè)類。

第二個(gè)步驟

public AndroidRefWatcherBuilder listenerServiceClass(
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }
  
  public ServiceHeapDumpListener(Context context,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    setEnabled(context, listenerServiceClass, true);
    setEnabled(context, HeapAnalyzerService.class, true);
    this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
    this.context = checkNotNull(context, "context").getApplicationContext();
  }
  
  public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
    this.heapDumpListener = heapDumpListener;
    return self();
  }
  
  @SuppressWarnings("unchecked")
  protected final T self() {
    return (T) this;
  }

接下來這個(gè)地方還是需要我們注意到,這里new了一個(gè)ServiceHeapDumpListener,在heapDumpListener方法里將new到listener賦值給了this.heapDumpListener
這里有一個(gè)小細(xì)節(jié)挺值得我們注意的,這里由于是泛型,所以不能直接返回this,這里統(tǒng)一返回了self()方法,統(tǒng)一在self方法里做強(qiáng)制轉(zhuǎn)換和unchecked操作。
通過這里的方法我們可以知道,我們將DisplayLeakService.class類,設(shè)置給了AndroidRefWatcherBuilder的一個(gè)自定義Listener變量。

第三個(gè)步驟:設(shè)置已知的內(nèi)存泄漏問題,或者系統(tǒng)的內(nèi)存泄漏問題

這里我們就看一下AndroidExcludedRefs.createAppDefaults().build()這個(gè)方法。

public static ExcludedRefs.Builder createAppDefaults() {
    return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
  }
  public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
    ExcludedRefs.Builder excluded = ExcludedRefs.builder();
    for (AndroidExcludedRefs ref : refs) {
      if (ref.applies) {
        ref.add(excluded);
        ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
      }
    }
    return excluded;
  }

到這里其實(shí)我們沒必要在往里面繼續(xù)看了,通過這兩個(gè)方法,大概可以看出這個(gè)是通過遍歷AndroidExcludedRefs.class類中定義的已知的一些系統(tǒng)級(jí)別的bug,得到一個(gè)集合,在后面發(fā)現(xiàn)內(nèi)存泄漏的時(shí)候會(huì)進(jìn)行忽略操作。

第四個(gè)步驟:開始進(jìn)行監(jiān)聽操作。

public RefWatcher buildAndInstall() {
    //只能創(chuàng)建一次
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //創(chuàng)建RefWatcher
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {

      LeakCanary.enableDisplayLeakActivity(context);
      //默認(rèn)為true
      if (watchActivities) {
        //注意,在這里通過監(jiān)聽Application,監(jiān)聽Activity的生命周期
        ActivityRefWatcher.install((Application) context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

可以看到,首先進(jìn)行了判空,單例的思想還是很重要的。
下面這個(gè)build()方法還是很重要的。

public final RefWatcher build() {
    // 判斷install是否在Analyzer進(jìn)程里,重復(fù)執(zhí)行
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
    //用于排除某些系統(tǒng)bug導(dǎo)致的內(nèi)存泄露
    ExcludedRefs excludedRefs = this.excludedRefs;
    if (excludedRefs == null) {
      excludedRefs = defaultExcludedRefs();
    }
    //用于分析生成的dump文件,找到內(nèi)存泄露的原因
    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }
    //用于查詢是否正在調(diào)試中,調(diào)試中不會(huì)執(zhí)行內(nèi)存泄露檢測
    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }
    //用于在產(chǎn)生內(nèi)存泄露室執(zhí)行dump 內(nèi)存heap
    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }
    //執(zhí)行內(nèi)存泄露檢測的executor
    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      //創(chuàng)建默認(rèn)的監(jiān)聽內(nèi)存泄漏的線程池
      watchExecutor = defaultWatchExecutor();
    }
    //用于在判斷內(nèi)存泄露之前,再給一次GC的機(jī)會(huì)
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }

    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        excludedRefs);
  }

這個(gè)build方法這里我們需要注意,我們?cè)谧铋_始的生成了AndroidRefWatcherBuilder,這個(gè)是繼承于RefWatcherBuilder類的,但是這里的build()方法是父類的方法,也就是RefWatcherBuilder的方法,但是在build()內(nèi)的許多調(diào)用都是AndroidRefWatcherBuilder重寫的方法。
首先來看第一個(gè)方法isDisabled()

@Override protected boolean isDisabled() {
    //用于判斷服務(wù)進(jìn)程是否在前臺(tái),重要
    return LeakCanary.isInAnalyzerProcess(context);
  }

可以看到,這里又用到了我們前面提到的一個(gè)方法,是用于判斷當(dāng)前進(jìn)程是否在前臺(tái),這里因?yàn)榉治鲋髁鞒?,所以先不做分析?/p>

@Override protected ExcludedRefs defaultExcludedRefs() {
    return AndroidExcludedRefs.createAppDefaults().build();
  }

接下來看到是設(shè)置了Android特有的一些系統(tǒng)的內(nèi)存泄漏,和前面分析的一致。

//用于分析生成的dump文件,找到內(nèi)存泄露的原因
    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }
    @Override protected HeapDump.Listener defaultHeapDumpListener() {
    return new ServiceHeapDumpListener(context, DisplayLeakService.class);
  }

因?yàn)樵?code>install()方法中已經(jīng)設(shè)置了用于發(fā)送內(nèi)存泄漏通知的Service,這里變不為null,不然其實(shí)default的和初始化的也是相同的。

//用于查詢是否正在調(diào)試中,調(diào)試中不會(huì)執(zhí)行內(nèi)存泄露檢測
    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }
    
    @Override protected DebuggerControl defaultDebuggerControl() {
    return new AndroidDebuggerControl();
  }
  
  public final class AndroidDebuggerControl implements DebuggerControl {
  @Override public boolean isDebuggerAttached() {
    return Debug.isDebuggerConnected();
  }
}

這里其實(shí)也挺值得我們學(xué)習(xí)的,這里當(dāng)是調(diào)試的時(shí)候,便不會(huì)進(jìn)行內(nèi)存泄漏檢測,而如何確定是在進(jìn)行調(diào)試,這里可以看到使用了Debug.isDebuggerConnected()方法。

//用于在產(chǎn)生內(nèi)存泄露室執(zhí)行dump 內(nèi)存heap
    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }
    
    @Override protected HeapDumper defaultHeapDumper() {
    LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
    return new AndroidHeapDumper(context, leakDirectoryProvider);
  }

這里就是生產(chǎn)內(nèi)存泄漏的文件的。

//執(zhí)行內(nèi)存泄露檢測的executor
    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      //創(chuàng)建默認(rèn)的監(jiān)聽內(nèi)存泄漏的線程池
      watchExecutor = defaultWatchExecutor();
    }
    
    @Override protected WatchExecutor defaultWatchExecutor() {
    //默認(rèn)線程池,5s
    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
  }
  
  public AndroidWatchExecutor(long initialDelayMillis) {
    //主線程Handler
    mainHandler = new Handler(Looper.getMainLooper());
    //這里new了一個(gè)HandlerThread,也就是一個(gè)異步線程,內(nèi)部封裝好了looper.prepare()等操作
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    //handlerThread內(nèi)部的handler
    backgroundHandler = new Handler(handlerThread.getLooper());
    this.initialDelayMillis = initialDelayMillis;
    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  }

可以看到這里雖然叫做看似像是線程池,其實(shí)也是利用了Android官方的基礎(chǔ)組件,這里可以看到快速的創(chuàng)建了一個(gè)主現(xiàn)場handler和一個(gè)HanlderThread,和HandlerThread內(nèi)部的Handler。具體HanlderThread是什么,這里放上這個(gè)類。

/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */
public class HandlerThread extends Thread {
@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
}

可以看到就是一個(gè)Thread只不過內(nèi)部封裝好了Android使用多線程Hanlder的一系列操作。

//用于在判斷內(nèi)存泄露之前,再給一次GC的機(jī)會(huì)
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }
    
    public interface GcTrigger {
  GcTrigger DEFAULT = new GcTrigger() {
    @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perfom a gc.
      //這里用的是Runtime.getRuntime().gc()
      //注意這里和System.gc()的區(qū)別
      Runtime.getRuntime().gc();
      //等待100毫秒
      enqueueReferences();
      System.runFinalization();
    }

    private void enqueueReferences() {
      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
      // references to the appropriate queues.
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        throw new AssertionError();
      }
    }
  };

  void runGc();
}

接下來算是LeakCanary的一個(gè)比較特殊的地方,接下來看到,設(shè)置了一個(gè)和GC相關(guān)的一個(gè)類,最終我們會(huì)發(fā)現(xiàn)是使用的上面放的DEFAULT,這里可以看到一個(gè)很特殊的一點(diǎn),這里使用了一個(gè)方法Runtime.getRuntime().gc(),而且也看到了官方對(duì)于此處的注釋這里引用了AOSP的一段代碼,System.gc()并不會(huì)每次都真正調(diào)用回收,所以使用Runtime.getRuntime().gc();這里就是我們平常不會(huì)注意到的知識(shí)點(diǎn),這里需要我們區(qū)分一下兩個(gè)的區(qū)別,我自己看了一下兩個(gè)的源碼,并沒有發(fā)現(xiàn)兩個(gè)內(nèi)部的不同(不知道是不是我看的方式的問題),我通過查詢,網(wǎng)上對(duì)于這兩個(gè)方法的區(qū)別總體是這樣解釋的。

/**
 * Indicates to the VM that it would be a good time to run the
 * garbage collector. Note that this is a hint only. There is no guarantee
 * that the garbage collector will actually be run.
 */
public static void gc() {
    boolean shouldRunGC;
    synchronized(lock) {
        shouldRunGC = justRanFinalization;
        if (shouldRunGC) {
            justRanFinalization = false;
        } else {
            runGC = true;
        }
    }
    if (shouldRunGC) {
        Runtime.getRuntime().gc();
    }
}

以上是引用一篇不錯(cuò)的LeakCanary的源碼分析中關(guān)于gc()源碼(不知道為什么我自己看不到這樣的源碼,如果有人知道,評(píng)論告訴我一下,謝謝~~),從這里就可以看出,System.gc()的實(shí)質(zhì)其實(shí)是調(diào)用Runtime.getRuntime().gc(),只不過做了一些多線程同步的判斷,所以,我們調(diào)用System.gc()并不會(huì)一定出發(fā)JVM的GC操作。
到此build()方法到這里就分析完了,通過上面的分析我們會(huì)發(fā)現(xiàn),到目前為止基本上都是做的準(zhǔn)備工作,接下來就是LeakCanary的核心操作,檢測內(nèi)存泄漏。

//開啟LeakCanary的Activity,使圖標(biāo)顯示
LeakCanary.enableDisplayLeakActivity(context);

  public static void enableDisplayLeakActivity(Context context) {
    setEnabled(context, DisplayLeakActivity.class, true);
  }
  
  public static void setEnabled(Context context, final Class<?> componentClass,
      final boolean enabled) {
    final Context appContext = context.getApplicationContext();
    executeOnFileIoThread(new Runnable() {
      @Override public void run() {
        setEnabledBlocking(appContext, componentClass, enabled);
      }
    });
  }
  
   public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
      boolean enabled) {
    ComponentName component = new ComponentName(appContext, componentClass);
    PackageManager packageManager = appContext.getPackageManager();
    int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
    // Blocks on IPC.
    packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
  }

下面這行代碼其實(shí)作用是開啟LeakCanary的應(yīng)用圖標(biāo),使其顯示。我們可以看到,這里傳入了DisplayLeakActivity.class類,最后通過packageManager.setComponentEnabledSetting這個(gè)方法,將Activity設(shè)置為COMPONENT_ENABLED_STATE_ENABLED狀態(tài)。這樣設(shè)置有什么作用哪,我們來看一下AndroidManifest.xml文件。

<activity
        android:theme="@style/leak_canary_LeakCanary.Base"
        android:name=".internal.DisplayLeakActivity"
        android:process=":leakcanary"
        android:enabled="false"
        android:label="@string/leak_canary_display_activity_label"
        android:icon="@mipmap/leak_canary_icon"
        android:taskAffinity="com.squareup.leakcanary.${applicationId}"
        >
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

可以看到,這里在清單文件中,將DisplayLeakActivityenabled。這里還有一個(gè)需要我們注意到點(diǎn),這里使用了線程池。

private static final Executor fileIoExecutor = newSingleThreadExecutor("File-IO");

public static void executeOnFileIoThread(Runnable runnable) {
    fileIoExecutor.execute(runnable);
  }
  
  public static Executor newSingleThreadExecutor(String threadName) {
    return Executors.newSingleThreadExecutor(new LeakCanarySingleThreadFactory(threadName));
  }

可以看到這里創(chuàng)建了Java中的newSingleThreadExecutor線程池,具體特點(diǎn)這里就不詳細(xì)介紹了,簡單的說就是一個(gè)唯一的線程,順序的執(zhí)行任務(wù)。

監(jiān)聽生命周期

//默認(rèn)為true
      if (watchActivities) {
        //注意,在這里通過監(jiān)聽Application,監(jiān)聽Activity的生命周期
        ActivityRefWatcher.install((Application) context, refWatcher);
      }

前面的一系列分析,這里終于可以開始監(jiān)聽生命周期,也就是檢測內(nèi)存泄漏的地方了。

public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }
  
  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    //注冊(cè)監(jiān)聽回調(diào)
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

  public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  }

可以看到這里避免重復(fù)監(jiān)聽,因?yàn)閮?nèi)部是使用一個(gè)ArrayList進(jìn)行保存lifecycleCallbacks,所以為了和之前的單例保持一致,這里就做移除操作。這里其實(shí)我們就要關(guān)注,這里是如果實(shí)現(xiàn)監(jiān)聽的。

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) {
            //onDestroy的時(shí)候回調(diào)
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

可以看到這里利用了Android中的Application的特性,注冊(cè)了Application.ActivityLifecycleCallbacks監(jiān)聽器,在Activity的onDestroy方法中,調(diào)用了ActivityRefWatcher.this.onActivityDestroyed(activity);方法。

void onActivityDestroyed(Activity activity) {
      //Activity在onDestroy的時(shí)候回調(diào)
    refWatcher.watch(activity);
  }

這里就調(diào)用了我們之前構(gòu)建的refWatcher對(duì)象的watch方法。

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    //獲得當(dāng)前時(shí)間
    final long watchStartNanoTime = System.nanoTime();
    //生成一個(gè)唯一的key
    String key = UUID.randomUUID().toString();
    //保存這個(gè)key
    retainedKeys.add(key);
    //將檢查內(nèi)存泄漏的對(duì)象保存為一個(gè)弱引用,注意queue
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //異步開始分析這個(gè)弱引用
    ensureGoneAsync(watchStartNanoTime, reference);
  }

這里可以看到,這里使用了當(dāng)前時(shí)間作為唯一標(biāo)示,這里獲取時(shí)間的方法也很講究,System.nanoTime()這個(gè)和System.currentTimeMillis()的區(qū)別也很簡答,這里引用別人的分析簡單的說明一下:

平時(shí)產(chǎn)生隨機(jī)數(shù)時(shí)我們經(jīng)常拿時(shí)間做種子,比如用System.currentTimeMillis的結(jié)果,但是在執(zhí)行一些循環(huán)中使用了System.currentTimeMillis,那么每次的結(jié)果將會(huì)差別很小,甚至一樣,因?yàn)楝F(xiàn)代的計(jì)算機(jī)運(yùn)行速度很快。后來看到j(luò)ava中產(chǎn)生隨機(jī)數(shù)函數(shù)以及線程池中的一些函數(shù)使用的都是System.nanoTime,下面說一下這2個(gè)方法的具體區(qū)別。
System.nanoTime提供相對(duì)精確的計(jì)時(shí),但是不能用他來計(jì)算當(dāng)前日期
System.nanoTime與System.currentTimeMillis的區(qū)別

接下來利用UUID.randomUUID().toString()生成了一個(gè)唯一的key保存在retainedKeys集合中,而這個(gè)retainedKeys是一個(gè)Set數(shù)據(jù)類型。

// 用于判斷弱引用所持有的對(duì)象是否已被GC,如果被回收,會(huì)存在隊(duì)列中,反之,沒有存在隊(duì)列中則泄漏了
  private final ReferenceQueue<Object> queue;
final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;
  public final String name;

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}

下面這個(gè)就是重點(diǎn)了,可以說是LeakCanary的核心,可以看到這里new了一個(gè)KeyedWeakReference對(duì)象,這里傳入了我們觀察的對(duì)象,也就是Activity,傳入了一個(gè)queue,而這個(gè)queue可以看到是一個(gè)ReferenceQueue。而這里KeyedWeakReference繼承了WeakReference,也就是我們熟知的弱引用,熟悉弱引用特性的應(yīng)該都知道,當(dāng)弱引用被回收的時(shí)候,會(huì)被放入一個(gè)隊(duì)列里,這里就是利用這個(gè)特性,使用弱引用持有一個(gè)Activity對(duì)象。

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

接下來就開始了真正開始分析的過程了,可以看到這里使用了我們前面創(chuàng)建的HandlerThread這個(gè)異步線程進(jìn)行操作。

  // 避免因?yàn)間c不及時(shí)帶來的誤判,leakcanay會(huì)手動(dòng)進(jìn)行g(shù)c,進(jìn)行二次確認(rèn)進(jìn)行保證
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    //System.currentTimeMillis,那么每次的結(jié)果將會(huì)差別很小,甚至一樣,因?yàn)楝F(xiàn)代的計(jì)算機(jī)運(yùn)行速度很快
    //檢測系統(tǒng)的耗時(shí)所用,所以使用System.nanoTime提供相對(duì)精確的計(jì)時(shí)
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //第一次判斷,移除此時(shí)已經(jīng)被回收的對(duì)象
    removeWeaklyReachableReferences();
    //調(diào)試的的時(shí)候是否開啟內(nèi)存泄漏判斷,默認(rèn)是false
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    //如果此時(shí)該對(duì)象已經(jīng)不再retainedKeys中說明第一次判斷時(shí)該對(duì)象已經(jīng)被回收,不存在內(nèi)存泄漏
    if (gone(reference)) {
      return DONE;
    }
    //如果當(dāng)前檢測對(duì)象還沒有被回收,則手動(dòng)調(diào)用gc
    gcTrigger.runGc();
    //再次做一次判斷,移除被回收的對(duì)象
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      //如果該對(duì)象仍然在retainedKey中,則說明內(nèi)存泄漏了,進(jìn)行分析
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      // dump出來heap,此時(shí)認(rèn)為內(nèi)存確實(shí)已經(jīng)泄漏了
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //開始分析
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

這里最先看到了removeWeaklyReachableReferences這個(gè)方法,也就是在Activity執(zhí)行了onDestroy之后,執(zhí)行這個(gè)方法,進(jìn)行第一次判斷

private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    //如果此時(shí)已經(jīng)在queue中,說明已經(jīng)被回收
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      //則從retainedKeys中移除
      retainedKeys.remove(ref.key);
    }
  }

這里可以看到,遍歷了剛才傳入了的弱應(yīng)用隊(duì)列,如果弱引用隊(duì)列中存在引用,說明改對(duì)象已經(jīng)被回收,然后通過存儲(chǔ)的唯一性key,從retainedKeys中移除。

//如果此時(shí)該對(duì)象已經(jīng)不再retainedKeys中說明第一次判斷時(shí)該對(duì)象已經(jīng)被回收,不存在內(nèi)存泄漏
    if (gone(reference)) {
      return DONE;
    }
    private boolean gone(KeyedWeakReference reference) {
    //retainedKeys不存在該對(duì)象的key
    return !retainedKeys.contains(reference.key);
  }

執(zhí)行完第一次判斷后,這里就判斷retainedKeys中是否存在該對(duì)象的key,如果不存在,說明該對(duì)象已經(jīng)成功被GC回收,則表明這時(shí)是不存在內(nèi)存泄漏的,則直接return.

//如果當(dāng)前檢測對(duì)象還沒有被回收,則手動(dòng)調(diào)用gc
    gcTrigger.runGc();
    //再次做一次判斷,移除被回收的對(duì)象
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      ...
    }
    
    
    GcTrigger DEFAULT = new GcTrigger() {
    @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perfom a gc.
      //這里用的是Runtime.getRuntime().gc()
      //注意這里和System.gc()的區(qū)別
      Runtime.getRuntime().gc();
      //等待100毫秒
      enqueueReferences();
      System.runFinalization();
    }
    。。。
  };

如果這時(shí)還存在retainedKeys說明可能存在內(nèi)存泄漏,熟悉GC的應(yīng)該都知道,GC的操作并不是實(shí)時(shí)的,所以第一次雖然該對(duì)象還沒有被回收,也可能是由于GC沒有觸發(fā)導(dǎo)致的,所以可以看到這里手動(dòng)觸發(fā)了GC操作,這里就要聯(lián)系到我們前面分析的Runtime.getRuntime().gc()。這樣就通了,這里手動(dòng)調(diào)用了Runtime.getRuntime().gc()方法,強(qiáng)制觸發(fā)GC。然后在執(zhí)行一次removeWeaklyReachableReferences();方法。再重復(fù)做一次判斷,弱引用是否被回收,存在于引用隊(duì)列中。

if (!gone(reference)) {
      //如果該對(duì)象仍然在retainedKey中,則說明內(nèi)存泄漏了,進(jìn)行分析
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      // dump出來heap,此時(shí)認(rèn)為內(nèi)存確實(shí)已經(jīng)泄漏了
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      //開始分析
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }

可以看到,當(dāng)強(qiáng)制GC后,進(jìn)行第二次判斷后,還是存在retainedKey中,這里就認(rèn)為產(chǎn)生了內(nèi)存泄漏,這時(shí)候就開始進(jìn)行分析,這里就利用了LeakCanary使用到的另一個(gè)庫Haha庫,用于分析引用路徑。首先這里的heapdumpListener的實(shí)現(xiàn)類就是我們前面提到的ServiceHeapDumpListener

@Override protected HeapDump.Listener defaultHeapDumpListener() {
    return new ServiceHeapDumpListener(context, DisplayLeakService.class);
  }
  
  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    //開啟HeapAnalyzerService,是一個(gè)HandlerService
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
  
  public static void  runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    //開啟一個(gè)IntentService用于分析內(nèi)存泄漏
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    //將回調(diào)的監(jiān)聽Service的class傳入,分析完成,回調(diào)到這個(gè)service
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    //收集的文件
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

這里我們就注意幾個(gè)關(guān)鍵點(diǎn)就行:

  1. 默認(rèn)創(chuàng)建的是ServiceHeapDumpListener,傳入了DisplayLeakService.class類對(duì)象。
  2. 執(zhí)行analyze方法的實(shí)質(zhì)就是開啟HeapAnalyzerService這個(gè)Service,并且將收集的heapDump傳入用于分析。
@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);

    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    //分析獲得結(jié)果,haha庫就在內(nèi)部調(diào)用的,注意分析
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    //回調(diào)結(jié)果
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

這時(shí)我們看一下HeapAnalyzerService的onHandleIntent方法,這里我們需要注意的就是heapAnalyzer.checkForLeak,這個(gè)方法就是LeakCanary內(nèi)部分析引用路徑的方法,內(nèi)部使用了Haha庫,當(dāng)然這個(gè)過程在一個(gè)IntentService中,當(dāng)然是異步的。

public static void sendResultToListener(Context context, String listenerServiceClassName,
      HeapDump heapDump, AnalysisResult result) {
    Class<?> listenerServiceClass;
    try {
      listenerServiceClass = Class.forName(listenerServiceClassName);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
    //啟動(dòng)Service通知,抽象類,DisplayLeakService
    Intent intent = new Intent(context, listenerServiceClass);
    //將分析的信息傳回給Service,發(fā)出內(nèi)存泄漏的通知
    intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
    intent.putExtra(RESULT_EXTRA, result);
    context.startService(intent);
  }

當(dāng)分析完結(jié)果后,可以看到這里,利用反射,創(chuàng)建了我們之前傳入的DisplayLeakService對(duì)象,然后將分析接口發(fā)送給DisplayLeakService

@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", leakInfo);

    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if (shouldSaveResult) {
      heapDump = renameHeapdump(heapDump);
      resultSaved = saveResult(heapDump, result);
    }

    PendingIntent pendingIntent;
    String contentTitle;
    String contentText;

    if (!shouldSaveResult) {
      //無泄露
      contentTitle = getString(R.string.leak_canary_no_leak_title);
      contentText = getString(R.string.leak_canary_no_leak_text);
      pendingIntent = null;
    } else if (resultSaved) {
      //獲得一個(gè)pendingIntent
      pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

      if (result.failure == null) {
        String size = formatShortFileSize(this, result.retainedHeapSize);
        String className = classSimpleName(result.className);
        if (result.excludedLeak) {
          contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
        } else {
          contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
        }
      } else {
        contentTitle = getString(R.string.leak_canary_analysis_failed);
      }
      contentText = getString(R.string.leak_canary_notification_message);
    } else {
      contentTitle = getString(R.string.leak_canary_could_not_save_title);
      contentText = getString(R.string.leak_canary_could_not_save_text);
      pendingIntent = null;
    }
    // New notification id every second.
    int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
    //顯示一個(gè)通知,顯示內(nèi)存泄漏
    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);
  }

這里由于DisplayLeakService繼承了AbstractAnalysisResultService,而AbstractAnalysisResultService繼承了IntentService,最終會(huì)調(diào)onHeapAnalyzed方法,這里可以看到當(dāng)存在內(nèi)存泄漏的時(shí)候,會(huì)創(chuàng)建一個(gè)pendingIntent用于后面通知的點(diǎn)擊事件跳轉(zhuǎn),而后發(fā)送了一個(gè)通知notification。

LeakCanary.isInAnalyzerProcess(context);

這里我們?cè)賮砜匆幌虑懊嫣岬降?code>LeakCanary提供給我們的一個(gè)比較不錯(cuò)的工具類,用于判斷當(dāng)前進(jìn)程是否在后臺(tái)。

public static boolean isInAnalyzerProcess(Context context) {
    Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
    // This only needs to be computed once per process.
    if (isInAnalyzerProcess == null) {
      //判斷進(jìn)程是否在后臺(tái),重要
      isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
      LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
    }
    return isInAnalyzerProcess;
  }
  
  public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
    PackageManager packageManager = context.getPackageManager();
    PackageInfo packageInfo;
    try {
      packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
    } catch (Exception e) {
      CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
      return false;
    }
    String mainProcess = packageInfo.applicationInfo.processName;

    ComponentName component = new ComponentName(context, serviceClass);
    ServiceInfo serviceInfo;
    try {
      serviceInfo = packageManager.getServiceInfo(component, 0);
    } catch (PackageManager.NameNotFoundException ignored) {
      // Service is disabled.
      return false;
    }

    if (serviceInfo.processName.equals(mainProcess)) {
      //如果服務(wù)進(jìn)程和主進(jìn)程是同一個(gè)進(jìn)程,那就不對(duì)了
      CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
      // Technically we are in the service process, but we're not in the service dedicated process.
      return false;
    }

    int myPid = android.os.Process.myPid();
    ActivityManager activityManager =
        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.RunningAppProcessInfo myProcess = null;
    List<ActivityManager.RunningAppProcessInfo> runningProcesses;
    try {
      runningProcesses = activityManager.getRunningAppProcesses();
    } catch (SecurityException exception) {
      // https://github.com/square/leakcanary/issues/948
      CanaryLog.d("Could not get running app processes %d", exception);
      return false;
    }
    if (runningProcesses != null) {
      for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
        //獲取當(dāng)前正在前臺(tái)對(duì)進(jìn)程
        if (process.pid == myPid) {
          myProcess = process;
          break;
        }
      }
    }
    if (myProcess == null) {
      CanaryLog.d("Could not find running process for %d", myPid);
      return false;
    }

    return myProcess.processName.equals(serviceInfo.processName);
  }

思路還是比較清晰的,遍歷當(dāng)前的所有運(yùn)行中的進(jìn)程,獲得當(dāng)前運(yùn)行的主進(jìn)程,然后和對(duì)應(yīng)的進(jìn)程名稱做對(duì)比。

總結(jié)

到此LeakCanary的整個(gè)流程已經(jīng)走完了,可能寫的比較瑣碎,但是大體流程還是比較清晰的。

1.通過Application監(jiān)聽Activity的生命周期
2.在Activity的Destroy時(shí),進(jìn)行內(nèi)存泄漏分析。
3.利用弱應(yīng)用的特性使用一個(gè)引用隊(duì)列保存Activity的引用,如果onDestroy后引用隊(duì)列中存在該Activity的實(shí)例則說明成功回收。
4.若不存在,則手動(dòng)利用Runtime.getRuntime().gc()方法手動(dòng)觸發(fā)GC,執(zhí)行完后再進(jìn)行一次判斷。
5.若此時(shí)還沒有在隊(duì)列中存在,說明沒有被回收,則認(rèn)定此時(shí)發(fā)生內(nèi)存泄漏。
6.異步執(zhí)行Haha庫進(jìn)行引用鏈分析,然后通知Service發(fā)出通知。

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 被文同時(shí)發(fā)布在CSDN上,歡迎查看。 APP內(nèi)存的使用,是評(píng)價(jià)一款應(yīng)用性能高低的一個(gè)重要指標(biāo)。雖然現(xiàn)在智能手機(jī)的內(nèi)...
    大圣代閱讀 4,956評(píng)論 2 54
  • Android 內(nèi)存管理的目的 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。簡單粗...
    晨光光閱讀 1,369評(píng)論 1 4
  • 接葉有多種,開花無異色。含露或低垂,從風(fēng)時(shí)偃仰?!挷臁栋俸稀?1、畫好底稿,畫完之后用針管筆描,用鉛筆的話,要...
    梨梧桐閱讀 1,135評(píng)論 0 16
  • 下午16: 00到達(dá)咸陽,正換衣服的時(shí)候,朋友同意給電影投資一千五百萬,并確定在4月5號(hào)前見面詳細(xì)溝通影片的運(yùn)營。...
    吳春飛閱讀 146評(píng)論 4 3
  • 開發(fā)中我們需要對(duì)輸入的一些值進(jìn)行校驗(yàn),如判斷是否是數(shù)字,收費(fèi)是漢字是否是特殊符號(hào),是否是電話號(hào)碼,是否是身份證號(hào)等...
    Coder_Cat閱讀 705評(píng)論 0 0

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