建議閱讀Android常見內(nèi)存泄漏這篇文章,本文的例子來源于文章中的內(nèi)存泄漏典型例子
內(nèi)存泄漏檢測工具
Profiler
其實(shí)Android studio自帶的 Profiler 是不錯(cuò)的,可以很直觀看到CPU、內(nèi)存、網(wǎng)絡(luò)的變化,但是有時(shí)候簡單看看是看不出來內(nèi)存泄漏的,需要知道具體怎么去分析
Android LeakCanary
Android LeakCanary易于集成,自動(dòng)檢測出內(nèi)存泄漏,十分好用
使用Profiler
以Android中的靜態(tài)變量為例
private static Activity sActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
sActivity = this;
findViewById(R.id.btn_back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
程序是這樣的,第一個(gè)Activity跳轉(zhuǎn)到第二個(gè)Activity,然后finish()返回第一個(gè)Activity,我們反復(fù)多做幾次;正常來講第二個(gè)Activity會(huì)被銷毀的,但是因?yàn)楸混o態(tài)變量引用了,所以應(yīng)該是無法被回收的;
使用Profiler來查看內(nèi)存泄漏
首先是點(diǎn)擊下面那一欄的Profiler按鈕,可能還沒有選擇程序,點(diǎn)擊+添加程序,這一步一般在我們操作程序前做,不然都沒記錄

會(huì)顯示CPU、內(nèi)存、網(wǎng)絡(luò)和能耗四個(gè)東西,點(diǎn)進(jìn)內(nèi)存里面去看詳情信息,其實(shí)只看內(nèi)存的大致情況不能得出什么結(jié)論,感覺好像沒什么問題

我們點(diǎn)擊那個(gè)箭頭符號(Dump Java heap),來捕獲堆轉(zhuǎn)儲(chǔ),堆轉(zhuǎn)儲(chǔ)顯示在您捕獲堆轉(zhuǎn)儲(chǔ)時(shí)您的應(yīng)用中哪些對象正在使用內(nèi)存,選擇按包名排序

然后選擇我們的程序,就可以看到哪些對象正在使用內(nèi)存

看見Main2Activity還在內(nèi)存中,證明它沒有被回收掉,內(nèi)存是發(fā)生了泄漏的,其中Main2Activity$1應(yīng)該是表示Main2Activity里面的第一個(gè)匿名內(nèi)部類對Main2Activity的引用,如果還有其他的匿名內(nèi)部類,就是$2、$3這樣排下去;
Heap Dump 右邊四列的意思分別如下,一般情況下,如果Shallow Size和Retained Size都非常小并且相等,都可以認(rèn)為是已經(jīng)被回收的對象。
- Allocations:Java堆中的實(shí)例個(gè)數(shù)
- Native Size:native層分配的內(nèi)存大小。
- Shallow Size:Java堆中分配實(shí)際大小
- Retained Size:這個(gè)類的所有實(shí)例保留的內(nèi)存總大?。ú⒎菍?shí)際大小)
點(diǎn)擊Heap Dump中的Main2Activity對象,發(fā)現(xiàn)右側(cè)出現(xiàn)了Instance View,再點(diǎn)擊Instance View中的對象,出現(xiàn)Reference和上圖一樣;Reference顯示對這個(gè)Main2Activity對象的引用,大部分都是系統(tǒng)層面的引用,可以看到第一個(gè)是sActivity這個(gè)靜態(tài)變量的引用,就說明是它引起的內(nèi)存泄漏;

還發(fā)現(xiàn)有很多this$0的引用,這個(gè)也往往是導(dǎo)致泄漏的原因,點(diǎn)進(jìn)去查看發(fā)現(xiàn)最終還是sActivity的引用;而出現(xiàn)多個(gè)this$0是因?yàn)槲曳磸?fù)操作了很多遍導(dǎo)致創(chuàng)建了很多個(gè)Main2Activity對象未被回收
在內(nèi)存泄漏檢查的過程中,我發(fā)現(xiàn)經(jīng)常出現(xiàn)過理論上對象肯定是被回收了,卻仍保留的情況。一般情況下,如果Shallow Size和Retained Size都非常小(比如我測試的一個(gè)空的activity,大概是270)并且相等,都可以認(rèn)為是已經(jīng)被回收的對象。因?yàn)橄到y(tǒng)已經(jīng)不認(rèn)為它會(huì)被用到,并且沒有給它保留分配的內(nèi)存。
使用Android LeakCanary
這個(gè)東西特別簡單,直接看官網(wǎng)就行了
就是GitHub地址:https://github.com/square/leakcanary
直接集成:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}
直接在Application中使用,然后運(yùn)行APP就會(huì)自動(dòng)檢測,檢測到會(huì)在另一個(gè)APP上通知,顯示詳情
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...
}
}
舉個(gè)栗子
以匿名內(nèi)部類為例,操作流程和之前的例子一樣;正常來講調(diào)用了finish()方法,第二個(gè)Activity會(huì)被銷毀的,但是因?yàn)槭褂昧四涿麅?nèi)部類,所以sRunnable會(huì)持有Main2Activity的引用,而且sRunnable還是一個(gè)靜態(tài)變量,所以會(huì)導(dǎo)致Main2Activity不會(huì)被回收掉
public class Main2Activity extends AppCompatActivity {
private static Thread sRunnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
sRunnable = new Thread() {
@Override
public void run() {
}
};
findViewById(R.id.btn_back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
}
運(yùn)行程序,然后過一會(huì)兒就會(huì)收到提醒,檢測到了內(nèi)存泄漏,打開看看;大概意思就是說sRunnable這個(gè)對象,它引用了Main2Activity,導(dǎo)致了內(nèi)存泄漏;這個(gè)工具的確非常的簡單友好了

參考文章: