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$0
,this$0
的含義就是內部類自動保留的一個指向所在外部類的引用,而這個外部類就是詳情最後一行所給出的MainActiviy的例項,這將會導致MainActivity無法被GC,從而產生記憶體洩漏。
除此之外,我們還可以將 heap dump(hprof檔案)和info資訊分享出去,如下圖所示。
需要注意的是分享出去的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相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。