###集合類泄漏
集合類如果僅僅有添加元素的方法,而沒有相應(yīng)的刪除機(jī)制,導(dǎo)致內(nèi)存被占用。如果這個集合類是全局性的變量 (比如類中的靜態(tài)屬性,全局性的 map 等即有靜態(tài)引用或 final 一直指向它),那么沒有相應(yīng)的刪除機(jī)制,很可能導(dǎo)致集合所占用的內(nèi)存只增不減。比如上面的典型例子就是其中一種情況,當(dāng)然實(shí)際上我們在項(xiàng)目中肯定不會寫這么 2B 的代碼,但稍不注意還是很容易出現(xiàn)這種情況,比如我們都喜歡通過 HashMap 做一些緩存之類的事,這種情況就要多留一些心眼。
###單例造成的內(nèi)存泄漏
由于單例的靜態(tài)特性使得其生命周期跟應(yīng)用的生命周期一樣長,所以如果使用不恰當(dāng)?shù)脑挘苋菀自斐蓛?nèi)存泄漏。比如下面一個典型的例子,
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
這是一個普通的單例模式,當(dāng)創(chuàng)建這個單例的時候,由于需要傳入一個Context,所以這個Context的生命周期的長短至關(guān)重要:
1、如果此時傳入的是 Application 的 Context,因?yàn)?Application 的生命周期就是整個應(yīng)用的生命周期,所以這將沒有任何問題。
2、如果此時傳入的是 Activity 的 Context,當(dāng)這個 Context 所對應(yīng)的 Activity 退出時,由于該 Context 的引用被單例對象所持有,其生命周期等于整個應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時它的內(nèi)存并不會被回收,這就造成泄漏了。
正確的方式應(yīng)該改為下面這種方式:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();// 使用Application 的context
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
或者這樣寫,連 Context 都不用傳進(jìn)來了:
在你的 Application 中添加一個靜態(tài)方法,getContext() 返回 Application 的 context,
...
context = getApplicationContext();
...
/**
* 獲取全局的context
* @return 返回全局context對象
*/
public static Context getContext(){
return context;
}
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager() {
this.context = MyApplication.getContext();// 使用Application 的context
}
public static AppManager getInstance() {
if (instance == null) {
instance = new AppManager();
}
return instance;
}
}
###匿名內(nèi)部類/非靜態(tài)內(nèi)部類和異步線程
非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏
有的時候我們可能會在啟動頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源,可能會出現(xiàn)這種寫法:
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
這樣就在Activity內(nèi)部創(chuàng)建了一個非靜態(tài)內(nèi)部類的單例,每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復(fù)創(chuàng)建,不過這種寫法卻會造成內(nèi)存泄漏,因?yàn)榉庆o態(tài)內(nèi)部類默認(rèn)會持有外部類的引用,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個靜態(tài)的實(shí)例,該實(shí)例的生命周期和應(yīng)用的一樣長,這就導(dǎo)致了該靜態(tài)實(shí)例一直會持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正常回收。正確的做法為:
將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。當(dāng)然,Application 的 context 不是萬能的,所以也不能隨便亂用,對于有些地方則必須使用 Activity 的 Context,對于Application,Service,Activity三者的Context的應(yīng)用場景如下:
其中: NO1表示 Application 和 Service 可以啟動一個 Activity,不過需要創(chuàng)建一個新的 task 任務(wù)隊(duì)列。而對于 Dialog 而言,只有在 Activity 中才能創(chuàng)建
###匿名內(nèi)部類
android開發(fā)經(jīng)常會繼承實(shí)現(xiàn)Activity/Fragment/View,此時如果你使用了匿名類,并被異步線程持有了,那要小心了,如果沒有任何措施這樣一定會導(dǎo)致泄露
public class MainActivity extends Activity {
...
Runnable ref1 = new MyRunable();
Runnable ref2 = new Runnable() {
@Override
public void run() {
}
};
...
}
ref1和ref2的區(qū)別是,ref2使用了匿名內(nèi)部類。我們來看看運(yùn)行時這兩個引用的內(nèi)存:
可以看到,ref1沒什么特別的。
但ref2這個匿名類的實(shí)現(xiàn)對象里面多了一個引用:
this$0這個引用指向MainActivity.this,也就是說當(dāng)前的MainActivity實(shí)例會被ref2持有,如果將這個引用再傳入一個異步線程,此線程和此Acitivity生命周期不一致的時候,就造成了Activity的泄露。
###Handler 造成的內(nèi)存泄漏
Handler 的使用造成的內(nèi)存泄漏問題應(yīng)該說是最為常見了,很多時候我們?yōu)榱吮苊?ANR 而不在主線程進(jìn)行耗時操作,在處理網(wǎng)絡(luò)任務(wù)或者封裝一些請求回調(diào)等api都借助Handler來處理,但 Handler 不是萬能的,對于 Handler 的使用代碼編寫一不規(guī)范即有可能造成內(nèi)存泄漏。另外,我們知道 Handler、Message 和 MessageQueue 都是相互關(guān)聯(lián)在一起的,萬一 Handler 發(fā)送的 Message 尚未被處理,則該 Message 及發(fā)送它的 Handler 對象將被線程 MessageQueue 一直持有。
由于 Handler 屬于 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一致的。因此這種實(shí)現(xiàn)方式一般很難保證跟 View 或者 Activity 的生命周期保持一致,故很容易導(dǎo)致無法正確釋放。
舉個例子:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
在該 SampleActivity 中聲明了一個延遲10分鐘執(zhí)行的消息 Message,mLeakyHandler 將其 push 進(jìn)了消息隊(duì)列 MessageQueue 里。當(dāng)該 Activity 被 finish() 掉時,延遲執(zhí)行任務(wù)的 Message 還會繼續(xù)存在于主線程中,它持有該 Activity 的 Handler 引用,所以此時 finish() 掉的 Activity 就不會被回收了從而造成內(nèi)存泄漏(因 Handler 為非靜態(tài)內(nèi)部類,它會持有外部類的引用,在這里就是指 SampleActivity)。
修復(fù)方法:在 Activity 中避免使用非靜態(tài)內(nèi)部類,比如上面我們將 Handler 聲明為靜態(tài)的,則其存活期跟 Activity 的生命周期就無關(guān)了。同時通過弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進(jìn)去,見下面代碼:
public class SampleActivity extends Activity {
/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static class MyHandler extends Handler {
private final WeakReference mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
綜述,即推薦使用靜態(tài)內(nèi)部類 + WeakReference 這種方式。每次使用前注意判空。
前面提到了 WeakReference,所以這里就簡單的說一下 Java 對象的幾種引用類型。
Java對引用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種。
在Android應(yīng)用的開發(fā)中,為了防止內(nèi)存溢出,在處理一些占用內(nèi)存大而且聲明周期較長的對象時候,可以盡量應(yīng)用軟引用和弱引用技術(shù)。
軟/弱引用可以和一個引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機(jī)就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。利用這個隊(duì)列可以得知被回收的軟/弱引用的對象列表,從而為緩沖器清除已失效的軟/弱引用。
假設(shè)我們的應(yīng)用會用到大量的默認(rèn)圖片,比如應(yīng)用中有默認(rèn)的頭像,默認(rèn)游戲圖標(biāo)等等,這些圖片很多地方會用到。如果每次都去讀取圖片,由于讀取文件需要硬件操作,速度較慢,會導(dǎo)致性能較低。所以我們考慮將圖片緩存起來,需要的時候直接從內(nèi)存中讀取。但是,由于圖片占用內(nèi)存空間比較大,緩存很多圖片需要很多的內(nèi)存,就可能比較容易發(fā)生OutOfMemory異常。這時,我們可以考慮使用軟/弱引用技術(shù)來避免這個問題發(fā)生。以下就是高速緩沖器的雛形:
首先定義一個HashMap,保存軟引用對象。
private Map > imageCache = new HashMap > ();
再來定義一個方法,保存Bitmap的軟引用到HashMap。
使用軟引用以后,在OutOfMemory異常發(fā)生之前,這些緩存的圖片資源的內(nèi)存空間可以被釋放掉的,從而避免內(nèi)存達(dá)到上限,避免Crash發(fā)生。
如果只是想避免OutOfMemory異常的發(fā)生,則可以使用軟引用。如果對于應(yīng)用的性能更在意,想盡快回收一些占用內(nèi)存比較大的對象,則可以使用弱引用。
另外可以根據(jù)對象是否經(jīng)常使用來判斷選擇軟引用還是弱引用。如果該對象可能會經(jīng)常使用的,就盡量用軟引用。如果該對象不被使用的可能性更大些,就可以用弱引用。
ok,繼續(xù)回到主題。前面所說的,創(chuàng)建一個靜態(tài)Handler內(nèi)部類,然后對 Handler 持有的對象使用弱引用,這樣在回收時也可以回收 Handler 持有的對象,但是這樣做雖然避免了 Activity 泄漏,不過 Looper 線程的消息隊(duì)列中還是可能會有待處理的消息,所以我們在 Activity 的 Destroy 時或者 Stop 時應(yīng)該移除消息隊(duì)列 MessageQueue 中的消息。
下面幾個方法都可以移除 Message:
public final void removeCallbacks(Runnable r);
public final void removeCallbacks(Runnable r, Object token);
public final void removeCallbacksAndMessages(Object token);
public final void removeMessages(int what);
public final void removeMessages(int what, Object object);
###盡量避免使用 static 成員變量
如果成員變量被聲明為 static,那我們都知道其生命周期將與整個app進(jìn)程生命周期一樣。
這會導(dǎo)致一系列問題,如果你的app進(jìn)程設(shè)計(jì)上是長駐內(nèi)存的,那即使app切到后臺,這部分內(nèi)存也不會被釋放。按照現(xiàn)在手機(jī)app內(nèi)存管理機(jī)制,占內(nèi)存較大的后臺進(jìn)程將優(yōu)先回收,yi'wei如果此app做過進(jìn)程互保保活,那會造成app在后臺頻繁重啟。當(dāng)手機(jī)安裝了你參與開發(fā)的app以后一夜時間手機(jī)被消耗空了電量、流量,你的app不得不被用戶卸載或者靜默。
這里修復(fù)的方法是:
不要在類初始時初始化靜態(tài)成員。可以考慮lazy初始化。 架構(gòu)設(shè)計(jì)上要思考是否真的有必要這樣做,盡量避免。如果架構(gòu)需要這么設(shè)計(jì),那么此對象的生命周期你有責(zé)任管理起來。
###避免 override finalize()
1、finalize 方法被執(zhí)行的時間不確定,不能依賴與它來釋放緊缺的資源。時間不確定的原因是: 虛擬機(jī)調(diào)用GC的時間不確定 Finalize daemon線程被調(diào)度到的時間不確定
2、finalize 方法只會被執(zhí)行一次,即使對象被復(fù)活,如果已經(jīng)執(zhí)行過了 finalize 方法,再次被 GC 時也不會再執(zhí)行了,原因是:
含有 finalize 方法的 object 是在 new 的時候由虛擬機(jī)生成了一個 finalize reference 在來引用到該Object的,而在 finalize 方法執(zhí)行的時候,該 object 所對應(yīng)的 finalize Reference 會被釋放掉,即使在這個時候把該 object 復(fù)活(即用強(qiáng)引用引用住該 object ),再第二次被 GC 的時候由于沒有了 finalize reference 與之對應(yīng),所以 finalize 方法不會再執(zhí)行。
3、含有Finalize方法的object需要至少經(jīng)過兩輪GC才有可能被釋放。
###資源未關(guān)閉造成的內(nèi)存泄漏
對于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,游標(biāo) Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷,否則這些資源將不會被回收,造成內(nèi)存泄漏。
###一些不良代碼造成的內(nèi)存壓力
有些代碼并不造成內(nèi)存泄露,但是它們,或是對沒使用的內(nèi)存沒進(jìn)行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內(nèi)存。
比如: Bitmap 沒調(diào)用 recycle()方法,對于 Bitmap 對象在不使用時,我們應(yīng)該先調(diào)用 recycle() 釋放內(nèi)存,然后才它設(shè)置為 null. 因?yàn)榧虞d Bitmap 對象的內(nèi)存空間,一部分是 java 的,一部分 C 的(因?yàn)?Bitmap 分配的底層是通過 JNI 調(diào)用的 )。 而這個 recyle() 就是針對 C 部分的內(nèi)存釋放。 構(gòu)造 Adapter 時,沒有使用緩存的 convertView ,每次都在創(chuàng)建新的 converView。這里推薦使用 ViewHolder。
##總結(jié)
對 Activity 等組件的引用應(yīng)該控制在 Activity 的生命周期之內(nèi); 如果不能就考慮使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部長生命周期的對象引用而泄露。
盡量不要在靜態(tài)變量或者靜態(tài)內(nèi)部類中使用非靜態(tài)外部成員變量(包括context ),即使要使用,也要考慮適時把外部成員變量置空;也可以在內(nèi)部類中使用弱引用來引用外部類的變量。
對于生命周期比Activity長的內(nèi)部類對象,并且內(nèi)部類中使用了外部類的成員變量,可以這樣做避免內(nèi)存泄漏:
將內(nèi)部類改為靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類中使用弱引用來引用外部類的成員變量
Handler 的持有的引用對象最好使用弱引用,資源釋放時也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的時候,取消掉該 Handler 對象的 Message和 Runnable.
在 Java 的實(shí)現(xiàn)過程中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地將此對象賦值為 null,比如使用完Bitmap 后先調(diào)用 recycle(),再賦為null,清空對圖片等資源有直接引用或者間接引用的數(shù)組(使用 array.clear() ; array = null)等,最好遵循誰創(chuàng)建誰釋放的原則。
正確關(guān)閉資源,對于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,游標(biāo) Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷。
保持對對象生命周期的敏感,特別注意單例、靜態(tài)對象、全局性集合等的生命周期。