Android LeakCanary的使用和原理

LeakCanary介紹

LeakCanary提供了一種很方便的方式,讓咱們在開發階段測試內存泄露,咱們不須要本身根據內存塊來分析內存泄露的緣由,咱們只須要在項目中集成他,而後他就會幫咱們檢測內存泄露,並給出內存泄露的引用鏈java

集成

  • 在gradle中添加依賴,這種不區分debug和release
implementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
複製代碼
  • 重寫Application
public class App extends Application {

    private RefWatcher mRefWatcher;

    @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;
        }

        mRefWatcher = LeakCanary.install(this);

    }


    public static RefWatcher getRefWatcher(Context context) {
        App application = (App) context.getApplicationContext();
        return application.mRefWatcher;
    }
}
複製代碼
  • 好比咱們在Activity中製造Handler發延遲消息的內存泄露
public class ActivityOne extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_one);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 100000);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}
複製代碼
  • 而後咱們打開這個activity而後再關閉,桌面就會出現一個leaks的圖標,而後咱們打開它
    在這裏插入圖片描述
    這就是基本使用,其實這種形式只能監視Activity的內存泄露,至於爲何只能監視Activity的內存泄露咱們下面再分析,若是想要監視其餘的內存泄露怎麼辦,好比要監視Fragment的內存泄露,能夠這樣寫,主動監視想要監視的對象
public abstract class BaseFragment extends Fragment {
 
  @Override public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = App.getRefWatcher(getActivity());
    refWatcher.watch(this);
  }
}
複製代碼

原理概述

經過監聽Activity的onDestory,手動調用GC,而後經過ReferenceQueue+WeakReference,來判斷Activity對象是否被回收,而後結合dump Heap的hpof文件,經過Haha開源庫分析泄露的位置android

主要的知識點

註冊Activity的生命週期的監聽器git

經過Application.registerActivityLifecycleCallbacks()方法註冊Activity的生命週期的監聽器,每個Actvity的生命週期都會回調到這個ActivityLifecycleCallbacks上,若是一個Activity走到了onDestory,那麼就意味着他就再也不存在,而後檢測這個Activity是不是真的被銷燬github

經過ReferenceQueue+WeakReference,來判斷對象是否被回收算法

WeakReference建立時,能夠傳入一個ReferenceQueue對象,假如WeakReference中引用對象被回收,那麼就會把WeakReference對象添加到ReferenceQueue中,能夠經過ReferenceQueue中是否爲空來判斷,被引用對象是否被回收app

詳細介紹推薦博客:www.jianshu.com/p/964fbc301…dom

MessageQueue中加入一個IdleHandler來獲得主線程空閒回調ide

這個知識點等以後寫一篇Handler源碼分析的時候在具體分析源碼分析

手動調用GC後還調用了System.runFinalization();,這個是強制調用已失去引用對象的finalize方法post

在可達性算法中,不可達對象,也不是非死不可,這時他們處於「緩刑」階段,要宣告一個對象真正死亡須要至少倆個標記階段, 若是發現對象沒有引用鏈,則會進行第一次標記,並進行一次篩選,篩選的條件是此對象是否有必要進行finalize()方法,當對象沒有覆蓋finalize(),或者finalize()已經調用過,這倆種都視爲「沒有必要執行」

Apolication中可經過processName判斷是不是任務執行進程

經過processName,來判斷進程

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)) {
      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);
  }
複製代碼

源碼分析

SDK初始化

mRefWatcher = LeakCanary.install(this);
複製代碼

這個是SDK向外暴露的方法,咱們以此爲入口進行源碼的分析

public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

 public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }
複製代碼

install方法首先初始化了一個AndroidRefWatcherBuilder類,而後經過listenerServiceClass方法設置了DisplayLeakService,這個類主要用於分析內存泄露的結果信息,而後發送通知給用戶

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {

  /** * Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This * overrides any call to {@link #heapDumpListener(HeapDump.Listener)}. */
  public AndroidRefWatcherBuilder listenerServiceClass( Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }
  ...
  }

public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
  ...
  /** @see HeapDump.Listener */
  public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
    this.heapDumpListener = heapDumpListener;
    return self();
  }
  ...
  }
複製代碼

而後調用excludedRefs方法添加白名單,在AndroidExcludedRefs枚舉類中定義了忽略列表信息,若是這些列表中的類發生了內存泄露,並不會顯示出來,同時HeapAnalyzer計算GCRoot強引用路徑,也會忽略這些類,若是你但願本身項目中某個類泄露的,可是不但願他顯示,就能夠把類添加到這個上面

public enum AndroidExcludedRefs {

  // ######## Android SDK Excluded refs ########

  ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
    @Override void add(ExcludedRefs.Builder excluded) {
      excluded.instanceField("android.app.ActivityThread$ActivityClientRecord", "nextIdle")
          .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.");
    }
  }
  ...
  }
複製代碼

最後調用了buildAndInstall方法,建立了一個RefWatcher對象並返回,這個對象是用於檢測是否有對象未被回收致使的內存泄露

/** * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+). */
  public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.install((Application) context, refWatcher);
    }
    return refWatcher;
  }
複製代碼

由於分析泄露是在另外一個進程進行的,因此判斷當前啓動的Application是否在分析內存泄露的進程中,若是是就直接返回DISABLED,不在進行後續初始化,若是發現是在程序主進程中,就進行初始化

LeakCanary.enableDisplayLeakActivity(context);主要做用是調用PackageManagerDisplayLeakActivity設置爲可用。

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);
  }
複製代碼

從配置文件看LeakCanary這幾個文件都是運行在新進程的,DisplayLeakActivity默認enable=false,這樣就能夠一開始隱藏啓動圖標

<application>
    <service
        android:name=".internal.HeapAnalyzerService"
        android:process=":leakcanary"
        android:enabled="false"/>
    <service
        android:name=".DisplayLeakService"
        android:process=":leakcanary"
        android:enabled="false"/>
    <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="@drawable/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>
    <activity
        android:theme="@style/leak_canary_Theme.Transparent"
        android:name=".internal.RequestStoragePermissionActivity"
        android:process=":leakcanary"
        android:taskAffinity="com.squareup.leakcanary.${applicationId}"
        android:enabled="false"
        android:excludeFromRecents="true"
        android:icon="@drawable/leak_canary_icon"
        android:label="@string/leak_canary_storage_permission_activity_label"/>
</application>
複製代碼

接着 ActivityRefWatcher.install((Application) context, refWatcher);這裏把refWatcher當作參數傳入,同時對Activity的生命週期進行了監聽

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();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }
  

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

 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) {
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };


  void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

複製代碼

首先就是註冊Activity的生命週期的監聽器lifecycleCallbacks,這個監聽器能夠監聽項目中全部Activity的的生命週期,而後在Activity銷燬調用onActivityDestroyed的時候,LeakCanary就會獲取這個Activity,而後對其進行分析,看是否有內存泄露

分析內存泄露

這裏分析對象是否內存泄露的是RefWatcher類,下面簡單介紹一下這個類的成員變量

  • WatchExecutor watchExecutor:確保任務在主線程進行,同時默認延遲5s執行任務,留時間給系統GC
  • DebuggerControl debuggerControl:控制中心
  • GcTrigger gcTrigger:內部調用Runtime.getRuntime().gc(),手動觸發GC
  • HeapDumper heapDumper:用於建立.hprof文件,用於儲存head堆快照,能夠知道哪些程序在大部分使用內存
  • HeapDump.Listener heapdumpListener:分析結果完成後會告訴這個監聽器
  • ExcludedRefs excludedRefs:分析內存泄露的白名單

從上面能夠看出,每當Activity銷燬,就會調用RefWatcherwatch方法,去分析是不是內存泄露

public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }

  public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
  }
複製代碼

上面代碼主要做用是,先生成一個隨機數key放在retainedKeys容器裏,用來區分對象是否被回收,建立了一個弱引用,而後把要分析的Activity對象存入,而後調用了ensureGoneAsync方法

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

而後用watchExecutor去調度分析任務,這個主要是保證,在主線程進行,延遲5s,讓系統有時間GC

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

    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) {
      return DONE;
    }
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

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



  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;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }
複製代碼

首先經過removeWeaklyReachableReferences()方法,嘗試從弱引用隊列獲取待分析對象,若是不爲空說明被系統回收了,就把retainedKeys中的key值移除,若是被系統回收直接返回DONE,若是沒有被系統回收,就手動調用 gcTrigger.runGc();手動觸發系統gc,而後再次調用removeWeaklyReachableReferences()方法,如過仍是爲空,則判斷爲內存泄露

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();
      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();
      }
    }
  };
複製代碼

手動觸發GC後,調用enqueueReferences方法沉睡100ms,給系統GC時間, System.runFinalization();,這個是強制調用已失去引用對象的finalize方法

肯定有內存泄漏後,調用heapDumper.dumpHeap();生成.hprof文件,而後回調到heapdumpListener監聽,這個監聽實現是ServiceHeapDumpListener類,會調analyze()方法

public final class ServiceHeapDumpListener implements HeapDump.Listener {
...
  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
}
複製代碼

HeapDump是一個modle類,裏面用於儲存一些分析類強引用的須要信息 HeapAnalyzerService.runAnalysis方法是發送了一個intent,啓動了HeapAnalyzerService服務,這是一個intentService

public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }
複製代碼

啓動服務後,會在onHandleIntent方法啓動分析,找到內存泄露的引用關係

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

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
複製代碼

找到引用關係

  • 啓動分析內存泄露的服務後,會實例化一個HeapAnalyzer對象,而後調用checkForLeak方法來分析最終獲得的結果,
  • checkForLeak這裏用到了Square的另外一個庫haha,哈哈哈哈哈,名字真的就是叫這個,開源地址:github.com/square/haha…
  • 獲得結果後調用AbstractAnalysisResultService.sendResultToListener()方法,這個方法啓動了另外一個服務
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);
    }
    Intent intent = new Intent(context, listenerServiceClass);
    intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
    intent.putExtra(RESULT_EXTRA, result);
    context.startService(intent);
  }
複製代碼

listenerServiceClassName就是開始LeakCanary.install方法傳入的DisplayLeakService,它自己也是一個intentService

@Override 
protected final void onHandleIntent(Intent intent) {
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
    AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
    try {
      onHeapAnalyzed(heapDump, result);
    } finally {
      //noinspection ResultOfMethodCallIgnored
      heapDump.heapDumpFile.delete();
    }
  }
複製代碼

而後調用自身的onHeapAnalyzed方法

protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", new Object[]{leakInfo});
    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if(shouldSaveResult) {
        heapDump = this.renameHeapdump(heapDump);
        resultSaved = this.saveResult(heapDump, result);
    }

    PendingIntent pendingIntent;
    String contentTitle;
    String contentText;

    // 設置消息通知的 pendingIntent/contentTitle/contentText
    ...

    int notificationId1 = (int)(SystemClock.uptimeMillis() / 1000L);
    LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId1);
    this.afterDefaultHandling(heapDump, result, leakInfo);
}
複製代碼

這個方法首先判斷是否須要把信息存到本地,若是須要就存到本地,而後設置消息通知的基本信息,最後經過LeakCanaryInternals.showNotification方法調用系統的系統通知欄,告訴用戶有內存泄露

至此LeakCanary的檢測內存泄露源碼,已經分析完了

參考:blog.csdn.net/xiaohanluo/…

juejin.im/post/5ab8d3…

相關文章
相關標籤/搜索