Android開發:詳解Handler的內存泄露
前言
-
內存泄露在Android開發中非常常見
-
內存泄露的定義:本該被回收的對象不能被回收而停留在堆內存中
-
內存泄露出現的原因:當一個對象已經不再被使用時,本該被回收但卻因為有另外一個正在使用的對象持有它的引用從而導致它不能被回收。
這就導致了內存泄漏。
-
本文將詳細講解內存泄露的其中一種情況:在Handler中發生的內存泄露
目錄
目錄
1. 背景
我們先來看下日常Handler的一般用法:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//主線程創建時便自動創建Looper和對應的MessageQueue,之前執行Loop()進入消息循環
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//實例化Handler
//這里并無指定Looper,即自動綁定當前線程(主線程)的Looper和MessageQueue
private Handler showhandler = new Handler(){
//通過復寫handlerMessage()從而決定如何進行更新UI操作
@Override
public void handleMessage(Message msg) {
//UI更新操作
}
};
//啟動子線程
new Thread(){
@Override
public void run() {
super.run();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
showhandler.sendEmptyMessageDelayed(0x1,10000);
}
}
}.start();
finish();
}
在上面的例子中,你會發現出現了嚴重的警告:
Paste_Image.png
從上圖可以看出來,這個警告的原因是:該Handler造成了嚴重的 內存泄漏
那么,該Handler是怎么樣造成內存泄露的呢?
2. 內存泄露原因
2.1 造成內存泄露的源頭
根據圖片可以分析,內存泄露顯示出現在:
-
Handler類
即Handler四件套:Looper+MessageQueue+Message+Handler
-
最終的泄露發生在Handler類的外部類 - MainActivity類
2.2 如何造成內存泄露
首先,我們需要了解到:
-
主線程的Looper對象會伴隨該應用程序的整個生命周期
-
在Java里, 非靜態內部類 和 匿名類 都會潛在引用它們所屬的外部類
在了解到上述兩條后,從上面的代碼中可以知道:
-
在發送的延遲空消息(EmptyMessageDelayed)后、消息處理被前,該消息會一直保存在主線程的消息隊列里持續10s
-
在這延時10s內,該消息內部持有對handler的引用,由于handler屬于非靜態內部類,所以又持有對其外部類(即MainActivity實例)的潛在引用,引用關系如下圖
引用關系
-
這條引用關系會一直保持直到消息得到處理,從而,這阻止了MainActivity被垃圾回收器(GC)回收,同時造成應用程序的內存泄漏,如下圖:
泄露分析
3. 解決方案
3.1 解決方案1:使用靜態內部類+弱引用
上面提到,在Java里, 非靜態內部類 和 匿名類 都會潛在的引用它們所屬的外部類。
但是, 靜態內部類不會。
所以,避免內存泄露的解決方案是:只需要將Handler的子類設置成靜態內部類
-
同時,還可以加上 使用WeakReference弱引用持有Activity實例
-
原因:弱引用的對象擁有短暫的生命周期。在垃圾回收器線程掃描時,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。
解決代碼如下:
public class MainActivity extends AppCompatActivity {
//將Handler改成靜態內部類
private static class FHandler extends Handler{
//定義弱引用實例
private WeakReference<Activity> reference;
//在構造方法中傳入需要持有的Activity實例
public MyHandler(Activity activity) {
reference = new WeakReference<Activity>(activity); }
//通過復寫handlerMessage()從而決定如何進行更新UI操作
@Override
public void handleMessage(Message msg) {
//省略代碼
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
//主線程創建時便自動創建Looper和對應的MessageQueue,之前執行Loop()進入消息循環
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//實例化Handler的子類
//這里并無指定Looper,即自動綁定當前線程(主線程)的Looper和MessageQueue
private final Handler showhandler = new FHandler();
//啟動子線程
new Thread(){
@Override
public void run() {
super.run();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
showhandler.sendEmptyMessageDelayed(0x1,10000);
}
}
}.start();
}
3.2 解決方案2:當外部類結束生命周期時清空消息隊列
-
從上面分析,內存泄露的原因是:
當Activity結束生命周期時,Handler里的Message可能還沒處理完,從而導致一系列的引用關系。 -
其實,我們只要在當Activity結束生命周期時清除掉消息隊列(MessageQueue)里的所有Message,那么這一系列引用關系就不會存在,就能防止內存泄露。
-
解決方案:當Activity結束生命周期時(調用onDestroy()方法),同時清除消息隊列里的所有回調消息(調用removeCallbacksAndMessages(null))
代碼如下:
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
經過上述兩個解決方案,在Handler里的內存泄露問題就不會再出現了!
4. 總結
-
本文總結的是關于Handler的一些小事:內存泄露,閱讀完本文后相信你已經懂得Handler內存泄露的原理和詳細的解決方案
-
接下來,我會繼續講解Android開發中關于Handler和多線程的知識,包括Handler源碼、繼承Thread類、實現Runnable接口、Handler等等。
來自:http://www.jianshu.com/p/ed9e15eff47a