Android中使用Handler為何造成記憶體洩漏?

曉涵說發表於2019-03-09

目錄: 1.記憶體洩漏定義 2.Handler造成記憶體洩漏的原因 3.優化方案

1.記憶體洩漏定義

首先我們需要了解Java中的常見記憶體分配,包括靜態儲存區(方法區)、棧和堆等。

靜態儲存區:儲存的是靜態方法和全域性的static資料和常量等,該區域的記憶體在程式編譯時已經分配完成,在程式執行的整個過程都存在。 棧區:在執行方法時,方法體內的區域性變數(包括基礎資料型別和物件的引用等)都在棧上建立,在方法執行結束後,該區域區域性變數所持有的記憶體會自動釋放。棧記憶體分配運算內建於處理器的指令集中,效率高,但該區域的容量有限。 堆區:又稱動態分配區,通常是儲存new出來的物件的例項,該部分記憶體在沒有引用時會由GC回收。

因此,我們通常說的記憶體洩漏是指:在堆區不斷的建立物件,在該物件已經使用結束,不會再使用該物件時,但是還存在別的物件(生命週期較長)引用,導致該物件無法及時被GC回收,導致堆區可使用的記憶體越來越少,導致記憶體洩漏的產生,最終的後果就是OOM。其實Android中的記憶體洩漏的原因與Java中類似:生命週期較長的物件持有生命週期較短的物件的引用。

2.Handler造成記憶體洩漏的原因

在Android中的跨執行緒互動時,尤其是子執行緒與UI執行緒互動時,通過Handler在子執行緒中傳送現象,Handler中做更新UI的操作,如下所示:

private Hand = new Handler(){
       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
           switch (msg.what){
               case UPDATE_UI:
                   //更新UI操作
                   break;
           }
       }
   };
複製程式碼

那麼,通過Handler為何可以在子執行緒傳送訊息,在handleMessage中可以執行更新UI的操作?我們知道通常只有在主執行緒(通常指UI執行緒)可以執行更新UI的操作,因此最終還是呼叫UI執行緒更新。呼叫流程分析如下:

2.1 建立Handler物件

檢視原始碼可以看到如下解釋,當我們建立Handler物件時,就與該執行緒和該執行緒的訊息佇列相繫結,如果未與當前執行緒和執行緒佇列繫結就無法正常執行事件的分發處理。我們在主執行緒建立Handler,因此就與主執行緒相繫結,Handler物件隱式的持有外部物件的引用,該外部物件通常是指Activity。

When you create a new Handler, it is bound to the thread / * message queue of the thread that is creating it

2.2訊息佇列處理

子執行緒執行處理結束後,通過Handler將訊息傳送處理,但如果此時當前介面已經銷燬(Activity銷燬),正常情況下,如果Activity不在使用,就可能被GC回收,但由於子執行緒仍未處理結束,仍持有Handler的引用(否則無法正常傳送Handler訊息),而該Handler持有外部Activty的引用,導致該Activity無法正常回收,導致記憶體的洩漏。 該引用的鏈如下: MessageQueue -> Message -> Handler -> Activity

3.優化方案

  1. Activity銷燬時及時清理訊息佇列;
  2. 自定義靜態Handler類+軟引用。

3.1 Activity銷燬時及時清理訊息佇列

在Activity銷燬時,呼叫removeCallbacksAndMessages清除Message和Runnable。

 mHandler.removeCallbacksAndMessages(null);
複製程式碼
/* * Remove any pending posts of callbacks and sent messages whose
     * <var>obj</var> is <var>token</var>.  If <var>token</var> is null,
     * all callbacks and messages will be removed.
     */
    public final void removeCallbacksAndMessages(Object token) {
        mQueue.removeCallbacksAndMessages(this, token);
    }
複製程式碼

3.2 自定義靜態Handler類+弱引用

static class MyHandler extends Handler {
        WeakReference<Activity> mWeakReference;
        private MyHandler(WeakReference<Activity> mWeakReference){
            this.mWeakReference = mWeakReference;
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(mWeakReference!=null){
                Activity activity = mWeakReference.get();
                if(activity!=null){
                    //handler訊息處理
                }
            }
        }
    }
複製程式碼

由3.1的分析中可以看出,Handler造成記憶體洩漏的主要原因是持有當前Activty的強引用,造成Activity無法及時被回收,我們知道GC處理弱引用的機制為當物件銷燬時,即使有弱引用存在,也會將其回收。

4.總結

避免使用Handler造成記憶體洩漏的方法如下:

  1. 在Activity的onDestory()方法中及時清理handler的訊息佇列;
  2. 自定義靜態Handelr類,避免非靜態內部類造成記憶體洩漏;
  3. 使用弱引用,使引用物件可以及時回收。

通過以上三種方式結合使用,可以有效的避免使用Handler不當,造成記憶體洩漏的情況。

相關文章