LeakCanary使用指南(1)

引言

  還在爲不會使用MAT而煩惱嗎?還在對着MAT工具解析出的hprof圖拼命找內存泄露的源頭嗎?放棄掙扎吧,少年。Android Studio時代,我們使用LeakCanary——傻瓜式的內存泄露檢測工具。如果你想了解引發Java內存泄露的深層原因,請移步Java 內存從分配到泄露 瞭解相關基礎知識。

簡介

  LeakCanary產自著名的Square公司,就是那個生產了網絡請求框架OkHttp、Retrofit、圖片加載框架Picasso的那個。LeakCanary可以讓你的App在Debug模式下發生內存泄露時主動彈框提醒,而在Release模式下什麼都不影響。

官網鏈接

  Github上LeakCanary的源碼首頁A memory leak detection library for Android and Java。如果你是個良好的英文閱讀者,那麼無需往下看,首頁上有你想要的一切。

快速集成

  第一步:在build.gradle中添加如下依賴:

dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
 }

  第二步:在自己的Application(假設名爲ExampleApplication)中添加如下代碼:

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;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

  到這裏其實可以檢測到Activity的內存泄露了,原理後面再說。以Debug模式運行你的App,你可以看到,你App的圖標後面跟着一個Leaks圖標,如下圖;而如果你以Release模式運行,則沒有這個圖標。
  

LeakCanary圖標

測試使用

  假裝你是測試人員,你開始各種點擊App,進行測試。然後你有幸看到這樣一個彈框,如下圖。
  

LeakCanary彈框

  你很好奇,然後點擊了彈框中間那個圖標,於是手機屏幕的左上角出現了你App的圖標,再下拉點擊那個圖標,或者從桌面上LeakCanary圖標(跟在你App的圖標屁股後面那個)點進去,你看到下圖。點擊+號可以展開,點擊-號收起。
  

LeakCanary泄露詳情

  內存泄露往往發生在,生命週期較長的對象,直接或間接持有了生命週期較短的對象的強引用,導致了生命週期較短的對象不能及時釋放
  上圖已經夠傻瓜式了,第一行表示生命週期較長的那個對象,圖中是AliPayModel這個類;第二行表示生命週期長的那個持有了一個什麼樣的引用,圖中是mActivity;第三行表示生命週期較短的那個對象,圖中是SelectPayTypeActivity。
  回去查看源碼,發現AliPayModel是個單例,在SelectPayTypeActivity中以AliPayModel.getInstance(this).XXX()的方式調用單例中的XXX()方法。於是AliPayModel通過mActivity持有了SelectPayTypeActivity.this的引用。SelectPayTypeActivity本來應該在用戶退出這個頁面和進入其他Activity(尤其是其他Activity層級較深時)時釋放掉,但是單例的生命週期貫穿整個App,AliPayModel一直引用着SelectPayTypeActivity,導致SelectPayTypeActivity不能及時釋放,引發內存泄露。

public class AliPayModel extends BasePayModel {
    private Activity mActivity;
    private AliPayModel() {}
    private static AliPayModel instance = new AliPayModel();
    public static AliPayModel getInstance(Activity tag) {
        instance.mActivity = tag;
        return instance;
    }

  找到了原因,解決方法也呼之欲出。要麼AliPayModel這個業務類不要定義成單例,要麼mActivity由強引用改成軟引用或者弱引用。Java的強、軟、弱、虛四種引用的區別不在本文的討論範圍。

發現開源組件中的內存泄露

  用上述方法,可以檢測出各種各樣的內存泄露,包括:WebView導致的內存泄露、資源未關閉導致的內存泄露、非靜態匿名內部類導致的內存泄露、Handler導致的內存泄露等等。
  請看下圖,每次選擇圖片、上傳頭像時都會引發0.96M的內存泄露!
  

開源組件泄露詳情

  再按圖索驥,發現罪魁禍首是將一個Activity定義爲static。表示不是很能理解這種神代碼。最讓人心中萬馬奔騰的是,它竟然有2600多個star!在這個項目的Issues中很多人反映內存佔用大、容易OOM、卡頓等,但是沒有人從技術層面去查找和分析原因,更遑論去閱讀源碼,都是直接拿來就用!
  如果你正打算替換該圖片選擇框架,不妨參考拍照/圖片選擇–裁剪–壓縮 一條龍這個項目。
  
GalleryFinal神代碼
  

總結

  經過簡單的配置,我們非常快速地發現了自己項目中存在的內存泄露的代碼,並且無意中發現了開源組件中的嚴重內存泄露問題。下次面試被問到內存泄露檢測工具,請不要只會習慣性地說MAT。
  但以上就是LeakCanary的全部嗎?遠遠不是!除了Activity,我們還可以監視哪些對象?我們可以不監視哪些Activity(怎樣添加例外)?怎樣定製自己的LeakCanary?
  Github上每個國外的牛逼項目,都會在README或者Wiki中詳細介紹它的使用、配置、實現原理等等,或者附上博客、網站的外鏈。如果你不習慣在官網獲得第一手資料,那麼本文的續篇LeakCanary使用指南(2)值得你期待!

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