Android記憶體優化(六)LeakCanary使用詳解

劉望舒發表於2017-09-09

相關文章
Android效能優化系列
Java虛擬機器系列

1.概述

如果使用MAT來分析記憶體問題,會有一些難度,並且效率也不是很高,對於一個記憶體洩漏問題,可能要進行多次排查和對比。
為了能夠簡單迅速的發現記憶體洩漏,Square公司基於MAT開源了LeakCanary

2.使用LeakCanary

首先配置build.gradle:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.2'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.2'
 }複製程式碼

接下來在Application加入如下程式碼。

public class LeakApplication extends Application {
    @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {//1
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
  }
}複製程式碼

註釋1處的程式碼用來進行過濾操作,如果當前的程式是用來給LeakCanary 進行堆分析的則return,否則會執行LeakCanary的install方法。這樣我們就可以使用LeakCanary了,如果檢測到某個Activity 有記憶體洩露,LeakCanary 就會給出提示。

3.LeakCanary應用舉例

第二節的例子程式碼只能夠檢測Activity的記憶體洩漏,當然還存在其他類的記憶體洩漏,這時我們就需要使用RefWatcher來進行監控。改寫Application,如下所示。

public class LeakApplication extends Application {
    private RefWatcher refWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher= setupLeakCanary();
    }
    private RefWatcher setupLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return RefWatcher.DISABLED;
        }
        return LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher(Context context) {
        LeakApplication leakApplication = (LeakApplication) context.getApplicationContext();
        return leakApplication.refWatcher;
    }
}複製程式碼

install方法會返回RefWatcher用來監控物件,LeakApplication中還要提供getRefWatcher靜態方法來返回全域性RefWatcher。
最後為了舉例,我們在一段存在記憶體洩漏的程式碼中引入LeakCanary監控,如下所示。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakThread leakThread = new LeakThread();
        leakThread.start();
    }
    class LeakThread extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(6 * 60 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = LeakApplication.getRefWatcher(this);//1
        refWatcher.watch(this);
    }
}複製程式碼

MainActivity存在記憶體洩漏,原因就是非靜態內部類LeakThread持有外部類MainActivity的引用,LeakThread中做了耗時操作,導致MainActivity無法被釋放。關於記憶體洩漏可以檢視Android記憶體優化(三)避免可控的記憶體洩漏這篇文章。
在註釋1處得到RefWatcher,並呼叫它的watch方法,watch方法的引數就是要監控的物件。當然,在這個例子中onDestroy方法是多餘的,因為LeakCanary在呼叫install方法時會啟動一個ActivityRefWatcher類,它用於自動監控Activity執行onDestroy方法之後是否發生記憶體洩露。這裡只是為了方便舉例,如果想要監控Fragment,在Fragment中新增如上的onDestroy方法是有用的。
執行程式,這時會在介面生成一個名為Leaks的應用圖示。接下來不斷的切換橫豎屏,這時會閃出一個提示框,提示內容為:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,記憶體洩漏資訊就會通過Notification展示出來,比如三星S8的通知欄如下所示。

Notification中提示了MainActivity發生了記憶體洩漏, 洩漏的記憶體為787B。點選Notification就可以進入記憶體洩漏詳細頁,除此之外也可以通過Leaks應用的列表介面進入,列表介面如下圖所示。

記憶體洩漏詳細頁如下圖所示。

點選加號就可以檢視具體類所在的包名稱。整個詳情就是一個引用鏈:MainActiviy的內部類LeakThread引用了LeakThread的this$0this$0的含義就是內部類自動保留的一個指向所在外部類的引用,而這個外部類就是詳情最後一行所給出的MainActiviy的例項,這將會導致MainActivity無法被GC,從而產生記憶體洩漏。

除此之外,我們還可以將 heap dump(hprof檔案)和info資訊分享出去,如下圖所示。

device-2017-09-02-181759_副本.png
device-2017-09-02-181759_副本.png

需要注意的是分享出去的hprof檔案並不是標準的hprof檔案,還需要將它轉換為標準的hprof檔案,這樣才會被MAT識別從而進行分析,關於MAT可以檢視Android記憶體優化(五)詳解記憶體分析工具MAT這篇文章。

解決方法就是將LeakThread改為靜態內部類。

public class MainActivity extends AppCompatActivity {
  ...
    static class LeakThread extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(6 * 60 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
...
}複製程式碼

再次執行程式LeakThread就不會給出記憶體洩漏的提示了。

參考資料
《高效能Android應用開發》
使用LeakCanary檢測安卓中的記憶體洩漏(實戰)
github.com/square/leak…
LeakCanary 中文使用說明
Android 原始碼系列之<十三>從原始碼的角度深入理解LeakCanary的記憶體洩露檢測機制(中)


歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。

相關文章