在使用java語音之前總聽人家說java是面向?qū)ο蟮模瑑?nèi)存只需要要申請不用釋放,當(dāng)我開始做Android用上java后我才知道java的回收其實(shí)是這樣的:
閑話不多說,我們來個(gè)栗子:
我直接從項(xiàng)目中抽取部分代碼來說明,代碼中涉及到三個(gè)Activity:LoadingActivity、MainActivity和SettingsActivity,三個(gè)Activity均包含一個(gè)大背景圖。LoadingActivity加載資源后啟動(dòng)MainActivity并finish掉自己,MainActivity點(diǎn)擊按鈕后跳轉(zhuǎn)到SettingsActivity。
三個(gè)Activity打開一遍,按回退鍵返回到桌面,然后執(zhí)行兩次GC(第一次釋放Activity對象,第二次釋放Activity對象引用的背景圖)后在Android studio中Monitors的Memory占用情況:
剩下的約35M內(nèi)存怎么GC都無法回收,重新啟動(dòng)app,在MainActivity中不打開SettingsActivity直接回退到桌面然后執(zhí)行GC,看到內(nèi)存占用是這樣的:
可以看出內(nèi)存占用不到20M了,證明SettingsActivity存在內(nèi)存泄漏了。
在Activity中添加打印也可以看到SettingsActivity調(diào)用了onDestroy()但在GC時(shí)并沒有被釋放(finalize()方法沒被調(diào)用)
插個(gè)題外話,這個(gè)app占用內(nèi)存這么大是因?yàn)槭褂脠D片背景,有人推薦用imege-loader,我也嘗試用了一下,使用默認(rèn)設(shè)置內(nèi)存占用是小了那么一點(diǎn)點(diǎn)(大概10m左右吧)不過我這項(xiàng)目占用主要就是三個(gè)大的背景圖,況且在調(diào)用釋放緩存后實(shí)際上內(nèi)存也并沒有立刻被回收,而且使用imege-loader就要在原本的布局上再嵌套一層放一個(gè)match_parent的ImageView我感覺這樣并沒有優(yōu)化我app的性能(貌似更嚴(yán)重了,多繪制一層的樣子),這些都是題外話,看官選擇性跳過就行。
我們在打開SettingsActivity后返回到桌面,執(zhí)行完GC后點(diǎn)擊Dump Java Heap:
這里我們可以看出來SettingsActivity之所以沒被釋放是因?yàn)槠鋬?nèi)部類DbUpdateListener有實(shí)例化對象(非靜態(tài)內(nèi)部類對象會持有父類的引用,Reference Tree第二行)沒有被回收。DbUpdateListener對象又被DatabaseUpdate的listener和SettingsActivity的dbUpdateListener引用,我們現(xiàn)在來看看代碼(代碼是從我項(xiàng)目中截取出關(guān)鍵部分):
public class SettingsActivity extends Activity{
? ? private DatabaseUpdate dbUpdate;
? ? private DbUpdateListener dbUpdateListener;
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? dbUpdate=DatabaseUpdate.get();
? ? ? ? dbUpdateListener=newDbUpdateListener();
? ? ? ? dbUpdate.setListener(dbUpdateListener);
? ? }
? ? @Override
? ? protected void onDestroy() {
? ? ? ? super.onDestroy();
? ? ? ? //findViewById(R.id.activity_settings).setBackgroundResource(0);
? ? ? ? Log.d(TAG,"onDestroy "+this);
? ? }
? ? private class DbUpdateListener implements DatabaseUpdateListener {
? ? ? ? ...
? ? }
? ? @Override
? ? protected voidfinalize()throwsThrowable {
? ? ? ? super.finalize();
? ? ? ? Log.d(TAG,"finalize "+this);
? ? }
}
public class DatabaseUpdate {
? ? private static DatabaseUpdateinstance;
? ? private DatabaseUpdateListenerlistener;
? ? public staticDatabaseUpdate get(){
? ? ? ? if(null==instance){
? ? ? ? ? ? synchronized(DatabaseUpdate.class) {
? ? ? ? ? ? ? ? if(null==instance)
? ? ? ? ? ? ? ? ? ? instance=new DatabaseUpdate();
? ? ? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return instance;
? ? }
}
從代碼中可以看出在SettingsActivity的onCreate方法中實(shí)例化了一個(gè)內(nèi)部類對象DbUpdateListener,將其注冊給了DatabaseUpdate對象,而DatabaseUpdate是一個(gè)單例,從而造成了SettingsActivity無法被回收的問題。在onDestroy()中將DbUpdateListener注銷掉后再試,內(nèi)存可以正常被回收了,Heap中SettingsActivity也沒有被引用了:
結(jié)尾再補(bǔ)個(gè)題外話,這個(gè)app內(nèi)存泄漏問題主要就是大背景圖的資源無法釋放,如果要加一個(gè)保險(xiǎn)機(jī)制的話就在onDestroy()中將背景設(shè)置為空(findViewById(R.id.activity_settings).setBackgroundResource(0)),這樣即使Activity沒被回收背景資源的回收也不會受影響。在程序中我嘗試過移除資源的引用后調(diào)用System.gc()并不能觸發(fā)GC(小米4 Android6.0),所以這個(gè)我感覺沒必要手動(dòng)調(diào)用。
為何要降低內(nèi)存消耗?其一是用戶使用會卡,其二就是系統(tǒng)優(yōu)先殺掉占用內(nèi)存大的。