1. 程式人生 > >LeakCanary 內存泄漏 監測 性能優化 簡介 原理

LeakCanary 內存泄漏 監測 性能優化 簡介 原理

tco aosp 多少 工作 play 發布 MIP 主線程 per

LeakCanary 內存泄漏 監測 性能優化 簡介 原理
GitHub:https://github.com/square/leakcanary
Demo地址:https://github.com/baiqiantao/LeakCanaryTest.git


目錄

目錄
簡單使用
更多介紹
自定義 LeakCanary
測試案例
Application
MainActivity
靜態成員導致的內存泄漏
單例導致的內存泄漏

簡單使用

A memory leak detection 內存泄露檢測 library for Android and Java.

A small leak will sink a great ship. -- Benjamin Franklin
千裏之堤, 毀於蟻穴。 -- 《韓非子·喻老》

添加依賴:

dependencies {
  debugImplementation ‘com.squareup.leakcanary:leakcanary-android:1.6.1‘
  debugImplementation ‘com.squareup.leakcanary:leakcanary-support-fragment:1.6.1‘ //當使用support庫時添加
  releaseImplementation ‘com.squareup.leakcanary:leakcanary-android-no-op:1.6.1‘ //發布時用的是無任何操作的版本
}

初始化:

LeakCanary.install(application);

配置完以後,在 debug 構建的版本中,如果檢測到某個 activity 或 fragment 有內存泄露,LeakCanary 就會自動地顯示一個通知。

更多介紹

  • 為什麽要使用LeakCanary

    • 內存泄漏是一種編程錯誤[programming error],會導致應用程序保留對不再需要的對象的引用。因此就會導致無法回收為該對象分配[allocated]的內存,最終導致 OutOfMemoryError crash。
    • 例如,Android Activity 實例在調用 onDestroy 方法後就不再需要了,但是如果在靜態字段中存儲了對該Activity的強引用將會阻止其被GC[garbage collected]
    • LeakCanary對一個 longer needed 的對象做了唯一標識,並找到阻止它被垃圾回收的引用鏈。
    • 當作者首次在Square公司的某款App中啟用 LeakCanary 後,他找到並修復了多個內存泄漏,並將 OutOfMemoryError 的崩潰率降低了94%。
  • LeakCanary是怎麽工作的

    • 通過 RefWatcher.watch() 創建了一個KeyedWeakReference to the watched object。
    • 然後在後臺線程檢查引用是否被清除了,如果沒有,則triggers a GC
    • 如果引用還是未被清除,則 dumps the heap 到文件系統中的 .hprof 文件中。
    • 在另外一個獨立的進程中啟動 HeapAnalyzerServiceHeapAnalyzer 使用 HAHA 解析 heap dump 。
    • 得益於唯一的 reference key, HeapAnalyzer 在 heap dump 中找到 KeyedWeakReference,並且定位 leaking reference。
    • HeapAnalyzer 計算到 GC roots 的最短強引用路徑,並確定是否有泄露。如果有的話,創建導致泄露的引用鏈。
    • 計算結果傳遞到 APP 進程中的 DisplayLeakService 中, 並以通知的形式展示出來。
  • 如何修復內存泄漏

    要修復某個內存泄漏,您需要查看該鏈並查找導致泄漏的那個引用,即在泄漏時哪個引用本應該被清除的。LeakCanary以紅色下劃線突出顯示可能導致泄漏的引用。

  • 如何復制 leak trace 信息

    可以通過 logcat 或通過 Leaks App 的菜單復制·

  • Android SDK可能導致泄漏嗎

    是。 在AOSP以及制造商實現中,已經存在許多已知的內存泄漏。 當發生這樣的泄漏時,作為應用程序開發人員,您幾乎無法解決此問題。
    出於這個原因,LeakCanary 有一個內置的已知Android漏洞列表可供忽略:AndroidExcludedRefs.java。

  • 如何通過 leak trace 挖掘泄漏信息

    有時 leak trace 是不夠的,您需要使用 MAT 或 YourKit 挖掘 heap dump。 以下是在堆轉儲中找到泄漏實例的方法:

    • 查找 com.squareup.leakcanary.KeyedWeakReference 的所有實例
    • 對於其中的每一個,請查看 key 字段。
    • 找到 key 字段等於 LeakCanary 報告的 the reference key 的 KeyedWeakReference
    • 找到的那個 KeyedWeakReference 的 referent 字段就是您內存泄漏的對象。
    • 此後,問題就掌握在你手中。A good start 是查看 GC Roots的最短路徑(除了弱引用)。
  • 如何修復構建錯誤

    • 如果leakcan-android不在Android Studio的 external libraries 列表中,但是 leakcanary-analyzer 和 leakcanary-watcher 卻存在在那裏:嘗試做一個 Clean Build。 如果仍然存在問題,請嘗試通過命令行構建。
    • error: package com.squareup.leakcanary does not exist: 如果您有其他 build types 而不是 debug 和 release,則還需要為這些構建類型添加特定的依賴項(xxxCompile)。
  • LeakCanary添加了多少個方法

    • 如果您使用ProGuard,答案為9或0。
    • LeakCanary 只應在調試版本中使用,並一定要在發布版本中禁用。我們為您的發布版本提供了一個特殊的空依賴項:leakcanary-android-no-op
    • LeakCanary 的完整版本更大,絕不應在您的 release 版本中發布。
  • 誰在推動 LeakCanary

    LeakCanary由 @pyricau創建並開源,目前由@jrodbx,@JakeWharton和@pyricau維護。

  • 為什麽叫 LeakCanary

    LeakCanary這個名稱是參考 煤礦中的金絲雀[canary in a coal mine],因為LeakCanary是一個用於通過提前預警危險[advance warning of a danger]來檢測風險[detect risks]的哨兵[sentinel]

  • Instant Run可能觸發無效的 leaks

    啟用Android Studio的即時運行功能可能會導致LeakCanary報告無效的內存泄漏。 請參閱 Android Issue Tracker 上的問題#37967114(https://issuetracker.google.com/issues/37967114)。

  • 我知道我有泄漏,為什麽通知不顯示

    你是否 attached to a debugger? LeakCanary在調試時忽略泄漏檢測以避免誤報。

自定義 LeakCanary

技術分享圖片

  • 如何觀察具有生命周期的對象

    在您的應用程序中,您可能有其他具有生命周期的對象,例如Fragment,Service,Dagger組件等。可以使用RefWatcher來監視應該進行垃圾回收的實例:refWatcher.watch(schrodingerCat);

  • 使用 no-op 依賴

    release 版本的leakcanary-android-no-op依賴項僅包含LeakCanary和RefWatcher類。如果您要自定義LeakCanary,您需要確保自定義僅出現在 debug 版本中,因為它可能會引用leakcanary-android-no-op依賴項中不存在的類。

  • 自定義圖標和標簽

    DisplayLeakActivity附帶了一個默認圖標和標簽,您可以通過在應用中提供R.mipmap.leak_canary_iconR.string.leak_canary_display_activity_label來更改它:

  • install 方法的默認邏輯

public static RefWatcher install(Application application) {
   return refWatcher(application)
      .listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}
  • 不監測特定的Activity
    默認情況下,LeakCanary會監視所有的Activity。 您可以自定義 installation steps 以執行不同的操作,例如忽略某種類型Activity的泄漏:
RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .watchActivities(false)
      .buildAndInstall();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
   @Override
   public void onActivityDestroyed(Activity activity) {
      if (activity instanceof IgnoreActivity) {
         return;
      }
      refWatcher.watch(activity);
   }
   //...
});
  • 在運行時打開和關閉 LeakCanary
refWatcher = LeakCanary.refWatcher(this)
      .heapDumper(getHeapDumper()) //在運行時開啟和關閉LeakCanary
      .buildAndInstall();
public TogglableHeapDumper getHeapDumper() {
 if (heapDumper == null) {
  LeakDirectoryProvider leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this);
  AndroidHeapDumper defaultDumper = new AndroidHeapDumper(this, leakDirectoryProvider);
  heapDumper = new TogglableHeapDumper(defaultDumper);
 }
 return heapDumper;
}
public class TogglableHeapDumper implements HeapDumper {
 private final HeapDumper defaultDumper;
 private boolean enabled = true;

 public TogglableHeapDumper(HeapDumper defaultDumper) {
  this.defaultDumper = defaultDumper;
 }

 public void setEnabled(boolean enabled) {
  this.enabled = enabled;
 }

 @Override
 public File dumpHeap() {
  return enabled ? defaultDumper.dumpHeap() : HeapDumper.RETRY_LATER;
 }
}
MyApplication.app().getHeapDumper().setEnabled(false);

測試案例

Application

public class MyApplication extends Application {
 private RefWatcher refWatcher;
 private static MyApplication app;
 private TogglableHeapDumper heapDumper;

 @Override
 public void onCreate() {
  super.onCreate();
  if (LeakCanary.isInAnalyzerProcess(this)) {
   Log.i("bqt", "此進程是專用於LeakCanary進行堆分析用的。您不應該在此進程中初始化您的應用。");
   return;
  }

  refWatcher = LeakCanary.refWatcher(this)
    .watchActivities(true) //默認為true,會監視所有Activity,你可以設置為false然後再指定要監測的Activity
    .watchFragments(true) //默認為true,會監視 native Fragment,如果添加了support依賴,則也會監視support中的Fragment
    .watchDelay(1, TimeUnit.SECONDS) //設置應該等待多長時間,直到它檢查跟蹤對象是否已被垃圾回收
    .maxStoredHeapDumps(7) //設置LeakCanary最多可以保存的 heap dumps 個數,默認為7
    .excludedRefs(getExcludedRefs()) //忽略特定的引用,這個垃圾東西設置後總是不生效
    .heapDumper(getHeapDumper()) //在運行時開啟和關閉LeakCanary
    //.listenerServiceClass() //可以更改默認行為以將 leak trace 和 heap dump 上載到您選擇的服務器。
    .buildAndInstall();
  app = this;
 }

 private ExcludedRefs getExcludedRefs() {
  return AndroidExcludedRefs.createAppDefaults()//經過大量測試,我感覺TMD完全忽略不了Activity和Fragment中內存泄漏
    .instanceField("com.bqt.test.Single", "imageView") //類名,字段名
    .staticField("com.bqt.test.StaticLeakActivity", "bitmap") //類名,靜態字段名
    .clazz("com.bqt.test.StaticLeakActivity") //忽略提供的類名的所有子類的所有字段和靜態字段
    .thread("Thread-10086") //忽略指定的線程,一般主線程名為【main】,子線程名為【Thread-整數】
    .build(); //忽略的引用如果又通過watch手動監測了,則仍會監測其內存泄漏情況
 }

 public static MyApplication app() {
  return app;
 }

 public RefWatcher getRefWatcher() {
  return refWatcher;
 }

 public TogglableHeapDumper getHeapDumper() {
  if (heapDumper == null) {
   LeakDirectoryProvider leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this);
   AndroidHeapDumper defaultDumper = new AndroidHeapDumper(this, leakDirectoryProvider);
   heapDumper = new TogglableHeapDumper(defaultDumper);
  }
  return heapDumper;
 }
}

MainActivity

public class MainActivity extends FragmentActivity implements AdapterView.OnItemClickListener {
 private FrameLayout frameLayout;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  ListView listView = new ListView(this);
  String[] array = {"靜態成員導致的內存泄漏",
    "單例導致的內存泄漏:Fragment",
    "禁用 LeakCanary",
    "",};
  listView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
  listView.setOnItemClickListener(this);
  frameLayout = new FrameLayout(this);
  frameLayout.setId(R.id.fragment_id);
  listView.addFooterView(frameLayout);
  setContentView(listView);
 }

 @Override
 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  switch (position) {
   case 0:
    startActivity(new Intent(this, StaticLeakActivity.class));
    break;
   case 1:
    getSupportFragmentManager().beginTransaction()
      .add(frameLayout.getId(), new SingleLeakFragment(), "SingleLeakFragment")
      .commit();
    break;
   case 2:
    MyApplication.app().getHeapDumper().setEnabled(false);
    break;
   default:
    break;
  }
 }
}

靜態成員導致的內存泄漏

public class StaticLeakActivity extends Activity {
 private static Bitmap bitmap;

 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  ImageView imageView = new ImageView(this);
  bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
  imageView.setImageBitmap(bitmap);
  setContentView(imageView);
 }
}

技術分享圖片
技術分享圖片

相關信息:

* com.bqt.test.StaticLeakActivity has leaked:
* InputMethodManager$ControlledInputConnectionWrapper.!(mParentInputMethodManager)!
* ? InputMethodManager.!(mLastSrvView)!
* ? PhoneWindow$DecorView.mContext
* ? StaticLeakActivity
* Reference Key: 7f96d2f1-bf17-47e2-84ad-cd5976d72766
* Device: HUAWEI HONOR PLK-UL00 PLK-UL00
* Android Version: 6.0 API: 23 LeakCanary: 1.6.1 26145bf
* Durations: watch=1007ms, gc=149ms, heap dump=1840ms, analysis=6567ms

單例導致的內存泄漏

public class SingleLeakFragment extends Fragment {
 private ImageView imageView;

 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  imageView = new ImageView(getContext());
  return imageView;
 }

 @Override
 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  super.onViewCreated(view, savedInstanceState);
  imageView.setImageResource(R.drawable.icon);
  Single.SINGLETON.setImageView(imageView);//單例中引用View同樣會導致Activity內存泄漏
 }
}
public enum Single {
 @SuppressLint("StaticFieldLeak")
 SINGLETON; //定義一個枚舉的元素,它就代表了Single的一個實例
 private ImageView imageView;

 public void setImageView(ImageView imageView) {
  this.imageView = imageView;
 }
}

技術分享圖片
技術分享圖片

相關信息:

* com.bqt.test.SingleLeakFragment has leaked:
* InputMethodManager$ControlledInputConnectionWrapper.!(mParentInputMethodManager)!
* ? InputMethodManager.!(mLastSrvView)!
* ? ListView.mOnItemClickListener
* ? MainActivity.mFragments
* ? FragmentController.mHost
* ? FragmentActivity$HostCallbacks.mFragmentManager
* ? FragmentManagerImpl.mAdded
* ? ArrayList.array
* ? array Object[].[0]
* ? SingleLeakFragment
* Reference Key: 4877bf10-596c-440f-b69c-5d239f670944
* Device: HUAWEI HONOR PLK-UL00 PLK-UL00
* Android Version: 6.0 API: 23 LeakCanary: 1.6.1 26145bf
* Durations: watch=17245ms, gc=138ms, heap dump=1675ms, analysis=8159ms

2018-10-2

LeakCanary 內存泄漏 監測 性能優化 簡介 原理