Handler可能造成內(nèi)存泄漏(四)
本文原創(chuàng),轉(zhuǎn)載請經(jīng)過本人準(zhǔn)許。
寫在前面:
不知不覺中我們已經(jīng)進(jìn)行了三篇有關(guān)Android消息機(jī)制的研究,溫故知新,我們先來回顧一下:
第一篇中我們探究了,在Android設(shè)計(jì)之時(shí),為何子線程允許更新UI。官方給出的解釋是由于線程安全(Thread Safe)問題。(當(dāng)然也有一些其他方面的猜想)
第二篇中,我們總結(jié)了三種崩潰解決的辦法。
Activity.runOnUIThread();
View.post();
Handler;
我們本著尋找最優(yōu)解的思路比較了三種解決辦法,發(fā)現(xiàn)代碼的實(shí)現(xiàn)方式都為Handler,從而真正引出了Android消息機(jī)制,Handler。
第三篇文章是我們這個(gè)系列的重頭戲,我用一個(gè)還算生動的故事,為大家解釋了Handler和相關(guān)核心類的關(guān)系。
OK,回顧之后,我們正式來開始本篇文章。
內(nèi)存泄漏是怎么一回事?
可以看到,標(biāo)題中有一個(gè)顯眼的名詞,就是內(nèi)存泄漏,我想有必要給初學(xué)的朋友們講講何為內(nèi)存泄漏。
在討論內(nèi)存泄漏之前,先簡單的說說Android中內(nèi)存的回收
Dalivik虛擬機(jī)扮演了常規(guī)的垃圾回收角色,為了GC能夠從App中及時(shí)回收內(nèi)存,我們需要時(shí)時(shí)刻刻在適當(dāng)?shù)臅r(shí)機(jī)來釋放引用對象,Dalvik的GC會自動把離開活動線程的對象進(jìn)行回收。
什么是Android內(nèi)存泄漏:
雖然Android是一個(gè)自動管理內(nèi)存的開發(fā)環(huán)境,但是垃圾回收器只會移除那些已經(jīng)失去引用的、不可達(dá)的對象,在十幾萬、幾十萬行代碼中,由于你的失誤使得一個(gè)本應(yīng)該被銷毀的對象仍然被錯誤的持有,那么該對象就永遠(yuǎn)不會被釋放掉,這些已經(jīng)沒有任何價(jià)值的對象,仍然占據(jù)聚集在你的堆內(nèi)存中,GC就會被頻繁觸發(fā),多說幾句,如果手機(jī)不錯,一次GC的時(shí)間70毫秒,不會對應(yīng)用的性能產(chǎn)生什么影響,但是如果一個(gè)手機(jī)的性能不是那么出色,一次GC時(shí)間120毫秒,出現(xiàn)大量的GC操作,我相信用戶就能感覺到了吧。這些無用的引用堆積在堆內(nèi)存中,越積越多最終導(dǎo)致Crash。
有關(guān)一些性能優(yōu)化推薦給大家一個(gè)我總結(jié)的博客。
扯得好像遠(yuǎn)了一點(diǎn),既然明白了內(nèi)存泄漏是怎么回事,那與Handler又有什么關(guān)系呢?
Handler造成的內(nèi)存泄漏
參考一篇外文:
inner-class-handler-memory-leak
當(dāng)我們正常使用Handler時(shí),會給出一個(gè)warning提示。翻譯過來就是,這個(gè)Handler類應(yīng)該是靜態(tài)的,否則可能造成內(nèi)存泄漏。
當(dāng)我們簡單的使用Handler的時(shí)候,并不會踩到內(nèi)存泄漏這個(gè)坑,不過當(dāng)Handler作為一個(gè)內(nèi)部類或者匿名類時(shí),這個(gè)問題就可能發(fā)生。
在上篇中,我們知道,當(dāng)Android啟動之時(shí),ActivityThread類中,會創(chuàng)建UI線程的Looper和MessageQueue
MessageQueue中的消息會被一個(gè)接一個(gè)處理。應(yīng)用的所有事件(比如Activity生命周期回調(diào)方法,按鈕點(diǎn)擊等等)都會被當(dāng)做一個(gè)消息對象放入到Looper的消息隊(duì)列中,然后再被逐一執(zhí)行。UI線程的Looper存在于整個(gè)應(yīng)用的生命周期內(nèi)。
當(dāng)在UI線程中創(chuàng)建Handler對象時(shí),它就會和UI線程中Looper的消息隊(duì)列進(jìn)行關(guān)聯(lián)。發(fā)送到這個(gè)消息隊(duì)列中的消息會持有這個(gè)Handler的引用,這樣當(dāng)Looper最終處理這個(gè)消息的時(shí)候framework就會調(diào)用Handler#handleMessage(Message)方法來處理具體的邏輯。
在Java中,非靜態(tài)的內(nèi)部類或者匿名類會隱式的持有其外部類的引用,而靜態(tài)的內(nèi)部類則不會。
那么,內(nèi)存到底是在哪里泄露的呢?其實(shí)泄漏發(fā)生的還是比較隱晦的,但是再看看下面這段代碼:
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();
}
}
當(dāng)activity被finish的時(shí)候,延遲發(fā)送的消息仍然會存活在UI線程的消息隊(duì)列中,直到10分鐘后它被處理掉。這個(gè)消息持有activity的Handler的引用,Handler又隱式的持有它的外部類(這里就是SampleActivity)的引用。這個(gè)引用會一直存在直到這個(gè)消息被處理,所以垃圾回收機(jī)制就沒法回收這個(gè)activity,內(nèi)存泄露就發(fā)生了。需要注意的是:15行的匿名Runnable子類也會導(dǎo)致內(nèi)存泄露。非靜態(tài)的匿名類會隱式的持有外部類的引用,所以context會被泄露掉。
解決這個(gè)問題也很簡單:在新的類文件中實(shí)現(xiàn)Handler的子類或者使用static修飾內(nèi)部類。靜態(tài)的內(nèi)部類不會持有外部類的引用,所以activity不會被泄露。如果你要在Handler內(nèi)調(diào)用外部activity類的方法的話,可以讓Handler持有外部activity類的弱引用,這樣也不會有泄露activity的風(fēng)險(xiǎn)。關(guān)于匿名類造成的泄露問題,我們可以用static修飾這個(gè)匿名類對象解決這個(gè)問題,因?yàn)殪o態(tài)的匿名類也不會持有它外部類的引用。
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<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(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)和非靜態(tài)內(nèi)部類的區(qū)別是非常微妙的,但這個(gè)區(qū)別是每個(gè)Android開發(fā)者應(yīng)該清楚的。那么底線是什么?如果要實(shí)例化一個(gè)超出activity生命周期的內(nèi)部類對象,避免使用非靜態(tài)的內(nèi)部類。建議使用靜態(tài)內(nèi)部類并且在內(nèi)部類中持有外部類的弱引用。