LeakCanary源碼分析

從sample項目中的入口開始查看:


public class ExampleApplication extends Application {

  @Override 
  public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    enabledStrictMode();
    LeakCanary.install(this);
  }

  private static void enabledStrictMode() {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
        .detectAll() //
        .penaltyLog() //
        .penaltyDeath() //
        .build());
  }
}

if (LeakCanary.isInAnalyzerProcess(this)) {

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

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

      return;

}

public static boolean isInAnalyzerProcess(Context context) {

    return isInServiceProcess(context, HeapAnalyzerService.class);

  }

  

  

   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);//獲取serviceClass服務信息

    } catch (PackageManager.NameNotFoundException ignored) {

      // Service is disabled.

      return false;

    }

    if (serviceInfo.processName.equals(mainProcess)) {

      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 =

        activityManager.getRunningAppProcesses();

    if (runningProcesses != null) {

      for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {

        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);

  }


前面是檢查當前app進程是否和內存分析服務的進程是否在同一進程,如果是,則不安裝內

存檢測。爲什麼是這樣子的呢?

查看內存分析的服務,發現前面有一段註釋

**
 * This service runs in a separate process to avoid slowing down the app process or making it run
 * out of memory.
 */
public final classHeapAnalyzerService extends IntentService

意思是這個服務運行在單獨的進程避免使當前app運行慢或者內存溢出.由此可見內存分析服務分析過程中佔用資源多

接下來是如果判斷不在當前進程中,則進行安裝.代碼如下

public final classLeakCanary {

  /**
   * Creates a {
@linkRefWatcher} that works out of the box, and starts watching activity
   * references (on ICS+).
   */
  
public staticRefWatcher install(Application application) {
    returnrefWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

 

首先創建一個AndoidRefWatchBuilder,這裏用到創建者模式.

/** Builder to create a customized {@linkRefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
  return newAndroidRefWatcherBuilder(context);
}

然後設置一個顯示分析內存分析後分析結果處理的監聽器.這個監聽器必須繼承抽象類AbstractAnalysisResultService

/**
 * Sets a custom {
@linkAbstractAnalysisResultService} to listen to analysis results. This
 * overrides any call to {
@link#heapDumpListener(HeapDump.Listener)}.
 */
public AndroidRefWatcherBuilderlistenerServiceClass(
    Class<? extendsAbstractAnalysisResultService> listenerServiceClass) {
  returnheapDumpListener(newServiceHeapDumpListener(context,listenerServiceClass));
}

 

AbstractAnalysisResultService又繼承了IntentService

public abstract classAbstractAnalysisResultServiceextends IntentService {

  private static finalString HEAP_DUMP_EXTRA= "heap_dump_extra";
  private static finalString RESULT_EXTRA= "result_extra";

ServiceHeapDumpListener代碼如下:

publicServiceHeapDumpListener(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();
}

裏面的setEnabled方法設置該服務可用(以前我們寫設置服務不可用,直接簡單粗暴的調用stopService方法,看大神寫的代碼才發現大神不愧是大神,考慮的那麼細緻).

public static voidsetEnabled(Context context, finalClass<?> componentClass,
    final boolean enabled) {
  finalContext appContext = context.getApplicationContext();
  executeOnFileIoThread(newRunnable() {
    @Overridepublic 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);
}

繼續往下看

/**
 * Creates a {
@linkRefWatcher} that works out of the box, and starts watching activity
 * references (on ICS+).
 */
public static RefWatcher install(Application application) {
  returnrefWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}

 

創建了一個ExcludedRefs.Builder的建造者。

/**
 * This returns the references in the leak path that can be ignored for app developers. This
 * doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs
 * in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app
 * developers except by resorting to serious hacks, so we remove the noise caused by those leaks.
 */
public static ExcludedRefs.Builder createAppDefaults() {
  returncreateBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}

添加了一個AndroidExcludedRefs.class

public staticExcludedRefs.BuildercreateBuilder(EnumSet<AndroidExcludedRefs> refs) {
  ExcludedRefs.Builder excluded = ExcludedRefs.builder();
  for (AndroidExcludedRefs ref : refs) {
    if(ref.applies) {
      ref.add(excluded);
      ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
    }
  }
  returnexcluded;
}

把所有要分析的AndroidExcludedRefs根據當前手機SDK版本和ROM作了過濾,註釋裏面也說得很清楚,隨着時間的推移,很多SDK 和廠商 ROM 中的內存泄露問題已經被儘快修復了

/**
 * This class is a work in progress. You can help by reporting leak traces that seem to be caused
 * by the Android SDK, here: https://github.com/square/leakcanary/issues/new
 *
 * We filter on SDK versions and Manufacturers because many of those leaks are specific to a given
 * manufacturer implementation, they usually share their builds across multiple models, and the
 * leaks eventually get fixed in newer versions.
 *
 * Most app developers should use {
@link#createAppDefaults()}. However, you can also pick the
 * leaks you want to ignore by creating an {
@linkEnumSet} that matches your needs and calling
 * {
@link#createBuilder(EnumSet)}
 */
@SuppressWarnings({"unused","WeakerAccess" }) // Public API.
public enum AndroidExcludedRefs

繼續往往下看..

/**
 * Creates a {
@linkRefWatcher} instance and starts watching activity references (on ICS+).
 */
public RefWatcherbuildAndInstall() {
  RefWatcher refWatcher = build();
  if (refWatcher !=DISABLED) {
    LeakCanary.enableDisplayLeakActivity(context);
    ActivityRefWatcher.install((Application)context,refWatcher);
  }
  returnrefWatcher;
}

這個和前面的設置服務調用的是同一個方法

public static voidenableDisplayLeakActivity(Context context) {
  setEnabled(context,DisplayLeakActivity.class, true);
}

 

public static voidinstall(Application application,RefWatcher refWatcher) {
  newActivityRefWatcher(application,refWatcher).watchActivities();
}

 

public voidwatchActivities() {
  // Make sure you don't get installed twice.
  stopWatchingActivities();
  application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

這裏註冊了一個activity什麼週期的回調

private finalApplication.ActivityLifecycleCallbackslifecycleCallbacks =
    newApplication.ActivityLifecycleCallbacks() {
      @Overridepublic void onActivityCreated(Activity activity,Bundle savedInstanceState) {
      }

      @Overridepublic void onActivityStarted(Activity activity) {
      }

      @Overridepublic void onActivityResumed(Activity activity) {
      }

      @Overridepublic void onActivityPaused(Activity activity) {
      }

      @Overridepublic void onActivityStopped(Activity activity) {
      }

      @Overridepublic void onActivitySaveInstanceState(Activity activity,Bundle outState) {
      }

      @Overridepublic void onActivityDestroyed(Activity activity) {
        ActivityRefWatcher.this.onActivityDestroyed(activity);
      }
    };

 

這個回調有什麼用呢? 這個回調可就厲害了,app裏面所有activity什麼週期的都會在回調,可看到這裏的回調方法都對應的activity的生命週期方法。舉個栗子,app裏面隨便在哪裏調用startActivity. Activity執行onCreate方法時會回調onActivityCreated()方法。以後寫退出app的輔助類是,就不用在BaseActivity裏面的onCreate()方法裏面添加當前activity,onDestory裏面移除當前activity。當然這只是這個回調在其他地方的用處。扯遠了.

 

最最關鍵的代碼來了.將要銷燬的activity添加到觀察裏面

voidonActivityDestroyed(Activity activity) {
  refWatcher.watch(activity);
}

調用

/**
 * Identical to {
@link#watch(Object, String)} with an empty string reference name.
 *
 *
@see#watch(Object, String)
 */
public void watch(Object watchedReference) {
  watch(watchedReference,"");
}

最終調用。生成一個隨機的UUID當做key。把key添加到retainedKeys set集合裏面,key和要檢測的對象封裝成KeyedWeakReference

/**
 * Watches the provided references and checks if it can be GCed. This method is non blocking,
 * the check is done on the {
@linkWatchExecutor} this {@linkRefWatcher} has been constructed
 * with.
 *
 *
@paramreferenceNameAn logical identifier for the watched object.
 */
public void watch(Object watchedReference,String referenceName) {
  if(this== DISABLED) {
    return;
  }
  checkNotNull(watchedReference,"watchedReference");
  checkNotNull(referenceName,"referenceName");

//通過兩個差值計算某些代碼執行時間,System.currentTimeMillis要精確
  final long watchStartNanoTime = System.nanoTime();
  String key = UUID.randomUUID().toString();
  retainedKeys.add(key);
  final KeyedWeakReference reference =
      newKeyedWeakReference(watchedReference,key,referenceName,queue);

  ensureGoneAsync(watchStartNanoTime,reference);
}

 

特定作了一個實驗

longstartcurrentTimeMillis=System.currentTimeMillis();//精確到毫秒
Thread.sleep(100);
System.out.println(System.currentTimeMillis()-startcurrentTimeMillis);

long startnanoTime=System.nanoTime();//精確到毫微秒
Thread.sleep(100);
System.out.println(System.nanoTime()-startnanoTime);

輸出結果

100

100126792

原來它就是可以精確到後面6位數.就和圓周率後面的小數點一樣,越多越精確。雖然說毫秒級的誤差應該是可以忽略不計的,但也讓我們知道了原來還有比毫秒更精確的一個方法的存在,說不定萬一哪天就遇到了呢。

繼續看代碼.

private voidensureGoneAsync(final longwatchStartNanoTime, finalKeyedWeakReference reference) {
  watchExecutor.execute(newRetryable() {
    @Overridepublic Retryable.Resultrun() {
      returnensureGone(reference,watchStartNanoTime);
    }
  });
}

最終檢測是否內存泄露的

@SuppressWarnings("ReferenceEquality")// Explicitly checking for named null.
Retryable.Result ensureGone(finalKeyedWeakReference reference, final longwatchStartNanoTime) {
  longgcStartNanoTime = System.nanoTime();
  long watchDurationMs =NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
  //查看弱引用所引用的對象是否被回收了,如果被回收了則retainedKeys集合裏面移除當前弱引用的key
  removeWeaklyReachableReferences();

  if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    returnRETRY;
  }

//判斷弱引用的key是否還在,如果不在了,則說明該對象已經被回收了
  if(gone(reference)) {
    returnDONE;
  }

//調用gc方法
  gcTrigger.runGc();

//再判斷
  removeWeaklyReachableReferences();

//如果對象依然存在,則說明該對象已經泄露了,然後獲取dumpHeap文件,分析dumHeap文件
  if (!gone(reference)) {
    longstartDumpHeap = System.nanoTime();
    long gcDurationMs =NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

    File heapDumpFile =heapDumper.dumpHeap();
    if (heapDumpFile ==RETRY_LATER) {
      // Could not dump the heap.
      returnRETRY;
    }
    longheapDumpDurationMs =NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

//解析dumHeap文件
    heapdumpListener.analyze(
        newHeapDump(heapDumpFile,reference.key,reference.name,excludedRefs,watchDurationMs,
            gcDurationMs,heapDumpDurationMs));
  }
  returnDONE;
}

 

private voidremoveWeaklyReachableReferences() {
  // 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;

//如果弱引用所引用的對象沒有被垃圾回收器回收了,queue.poll()返回null
  while ((ref = (KeyedWeakReference)queue.poll()) !=null) {
    retainedKeys.remove(ref.key);
  }
}

//檢查當前引用的key是否在集合當中

private booleangone(KeyedWeakReference reference) {
  return!retainedKeys.contains(reference.key);
}

如果在還集合當中,運行gc

@Overridepublic 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();
  enqueueReferences();
  System.runFinalization();
}

 

最費解的當屬弱引用和其引用列隊的聯合使用,然後判斷當前弱引用所應用的對象是否被回收了。

這裏特例舉個栗子說明弱引用如何判斷所引用的對象是否被回收了

 

Person person=newPerson();
        ReferenceQueue queue=newReferenceQueue();
        final KeyedWeakReference reference =
                newKeyedWeakReference(person,"123","456",queue);
        person=null;//顯示將person置爲null
        System.out.println("queue.poll():"+queue.poll());
//        System.out.println("queue.get():"+reference.get());
        System.gc();
        try {
            Thread.sleep(500);
        }catch (InterruptedException e) {
            throw newAssertionError();
        }
        System.runFinalization();
        System.out.println("queue.poll():"+queue.poll());

 

 

輸出結果

queue.poll():null

finalize.....

queue.poll():com.example.lib.KeyedWeakReference@75b84c92

當我們將person置爲null時,垃圾回收器並不會立即回收person對象,所以第一個poll的結果是null.當我們調用gc方法後,垃圾回收器發現personnull(因爲調用gc後垃圾回收器並不會立即執行,所以加延遲),於是就回收person對象,所以第二次打印的結果就不一樣了。


感謝一下文章:

https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章