Android開發:詳解Handler的記憶體洩露
原文:https://blog.csdn.net/carson_ho/article/details/52693211
前言
記憶體洩露在Android開發中非常常見
- 記憶體洩露的定義:本該被回收的物件不能被回收而停留在堆記憶體中
- 記憶體洩露出現的原因:當一個物件已經不再被使用時,本該被回收但卻因為有另外一個正在使用的物件持有它的引用從而導致它不能被回收。
這就導致了記憶體洩漏。
本文將詳細講解記憶體洩露的其中一種情況:在Handler中發生的記憶體洩露
閱讀本文前建議先閱讀Android開發:Handler非同步通訊機制全面解析(包含Looper、Message Queue)
目錄
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();
}
在上面的例子中,你會發現出現了嚴重的警告:
從上圖可以看出來,這個警告的原因是:該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等等,有興趣可以繼續關注Carson_Ho的安卓開發筆記
請點贊!因為你的鼓勵是我寫作的最大動力!
相關文章閱讀
1分鐘全面瞭解“設計模式”
Android開發:最全面、最易懂的Android螢幕適配解決方案
Android開發:Handler非同步通訊機制全面解析(包含Looper、Message Queue)
Android開發:頂部Tab導航欄實現(TabLayout+ViewPager+Fragment)
Android開發:底部Tab選單欄實現(FragmentTabHost+ViewPager)
Android開發:JSON簡介及最全面解析方法!
Android開發:XML簡介及DOM、SAX、PULL解析對比