本文翻譯自:How to Leak a Context: Handlers & Inner Classes
在Android日常編程中怖侦,Handler在進(jìn)行異步操作并處理返回結(jié)果時(shí)經(jīng)常被使用。通常我會(huì)用這樣的代碼實(shí)現(xiàn)
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}
其實(shí)不太明顯的是馅扣,上面的代碼可能導(dǎo)致嚴(yán)重的內(nèi)存泄露,Android lint工具會(huì)給你如下警告:
In Android, Handler classes should be static or leaks might occur.
Issue: Ensures that Handler classes do not hold on to a reference to an outer class
Id: HandlerLeak
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected.
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue.
If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration,
as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer
class and pass this object to your Handler when you instantiate the Handler; Make all references to members
of the outer class using the WeakReference object.
但是究竟是哪里泄漏了,它又是如何發(fā)生的呢向族?首先讓我們通過(guò)已知的文檔來(lái)探個(gè)究竟:
When an Android application first starts, the framework creates a Looper object for the application’s main thread. A Looper implements a simple message queue, processing Message objects in a loop one after another. All major application framework events (such as Activity lifecycle method calls, button clicks, etc.) are contained inside Message objects, which are added to the Looper’s message queue and are processed one-by-one. The main thread’s Looper exists throughout the application’s lifecycle.
When a Handler is instantiated on the main thread, it is associated with the Looper’s message queue. Messages posted to the message queue will hold a reference to the Handler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message.
In Java, non-static inner and anonymous classes hold an implicit reference to their outer class. Static inner classes, on the other hand, do not.
- 1.當(dāng)一個(gè)Android應(yīng)用啟動(dòng)的時(shí)候,會(huì)自動(dòng)創(chuàng)建一個(gè)供應(yīng)用主線程使用的Looper實(shí)例说搅。Looper的主要工作就是一個(gè)一個(gè)處理消息隊(duì)列中的消息對(duì)象炸枣。在Android中,所有Android框架的事件(比如Activity的生命周期方法調(diào)用和按鈕點(diǎn)擊等)都是放入到消息中,然后加入到Looper要處理的消息隊(duì)列中适肠,由Looper負(fù)責(zé)一條一條地進(jìn)行處理霍衫。主線程中的Looper生命周期和當(dāng)前應(yīng)用一樣長(zhǎng)。
- 2.當(dāng)一個(gè)Handler在主線程進(jìn)行了初始化之后侯养,我們發(fā)送一個(gè)target為這個(gè)Handler的消息到Looper處理的消息隊(duì)列時(shí)敦跌,實(shí)際上已經(jīng)發(fā)送的消息已經(jīng)包含了一個(gè)Handler實(shí)例的引用,只有這樣Looper在處理到這條消息時(shí)才可以調(diào)用Handler#handleMessage(Message)完成消息的正確處理逛揩。
- 3.在Java中柠傍,非靜態(tài)的內(nèi)部類和匿名內(nèi)部類都會(huì)隱式地持有其外部類的引用。靜態(tài)的內(nèi)部類不會(huì)持有外部類的引用辩稽。
所以看出哪里內(nèi)存泄漏了嗎惧笛?它十分巧妙,讓我們一起思考如下示例代碼
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)我們執(zhí)行了Activity的finish方法逞泄,被延遲的消息會(huì)在被處理之前存在于主線程消息隊(duì)列中10分鐘患整,而這個(gè)消息中又包含了Handler的引用,而Handler是一個(gè)匿名內(nèi)部類的實(shí)例喷众,其持有外面的SampleActivity的引用各谚,所以這導(dǎo)致了SampleActivity無(wú)法回收,進(jìn)行導(dǎo)致SampleActivity持有的很多資源都無(wú)法回收到千,這就是我們常說(shuō)的內(nèi)存泄露昌渤。
注意上面的new Runnable這里也是匿名內(nèi)部類實(shí)現(xiàn)的,同樣也會(huì)持有SampleActivity的引用憔四,也會(huì)阻止SampleActivity被回收膀息。
要解決這種問(wèn)題,就不能用非靜態(tài)內(nèi)部類了赵,繼承Handler時(shí)履婉,要么是放在單獨(dú)的類文件中,要么就是使用靜態(tài)內(nèi)部類斟览。因?yàn)殪o態(tài)的內(nèi)部類不會(huì)持有外部類的引用毁腿,所以不會(huì)導(dǎo)致外部類實(shí)例的內(nèi)存泄露。當(dāng)你需要在靜態(tài)內(nèi)部類中調(diào)用外部的Activity時(shí)苛茂,我們可以使用弱引用來(lái)處理已烤。另外關(guān)于同樣也需要將Runnable設(shè)置為靜態(tài)的成員屬性。注意:一個(gè)靜態(tài)的匿名內(nèi)部類實(shí)例不會(huì)持有外部類的引用妓羊。 修改后不會(huì)導(dǎo)致內(nè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();
}
}
其實(shí)在Android中很多的內(nèi)存泄露都是由于在Activity中使用了非靜態(tài)內(nèi)部類導(dǎo)致的胯究,就像本文提到的一樣,所以當(dāng)我們使用時(shí)要非靜態(tài)內(nèi)部類時(shí)要格外注意躁绸,如果其實(shí)例的持有對(duì)象的生命周期大于其外部類對(duì)象裕循,那么就有可能導(dǎo)致內(nèi)存泄露臣嚣。個(gè)人傾向于使用文章的靜態(tài)類和弱引用的方法解決這種問(wèn)題。