Android中Handler內(nèi)存泄漏分析及解決

1.什么是內(nèi)存泄漏?

Java使用有向圖機(jī)制,通過GC自動檢查內(nèi)存中的對象(什么時候檢查由虛擬機(jī)決定),如果GC發(fā)現(xiàn)一個或一組對象為不可到達(dá)狀態(tài),則將該對象從內(nèi)存中回收。也就是說,一個對象不被任何引用所指向,則該對象會在被GC發(fā)現(xiàn)的時候被回收;另外,如果一組對象中只包含互相的引用,而沒有來自它們外部的引用(例如有兩個對象A和B互相持有引用,但沒有任何外部對象持有指向A或B的引用),這仍然屬于不可到達(dá),同樣會被GC回收。

Java內(nèi)存泄漏指的是進(jìn)程中某些對象(垃圾對象)已經(jīng)沒有使用價值了,但是它們卻可以直接或間接地引用到導(dǎo)致無法被GC回收。無用的對象占據(jù)著內(nèi)存空間,使得實際可使用內(nèi)存變小,形象地說法就是內(nèi)存泄漏了。

2.Android中使用Handler造成內(nèi)存泄露原因分析

(1)Handler使用方法

Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

在使用handler時,這是一段很常見的代碼。但是,它卻會造成嚴(yán)重的內(nèi)存泄漏問題。在實際編寫中,我們往往會得到如下警告:

This Handler class should be static or leaks might occur (anonymous android.os.Handler)

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.

(2)內(nèi)部類mHandler

簡單的內(nèi)部類如下:

class OuterClass { 
    class InnerClass{ 
    }
}

以上代碼mHandler讓人并不覺得是內(nèi)部類,它并不像InnerClass那樣形象,但是其實以下句柄實現(xiàn)一個繼承Handler的類,也就是自定義了一個類,那么明顯它就是一個內(nèi)部類。其實它是屬于內(nèi)部類一種:匿名內(nèi)部類Anonymous Inner Class

{
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
}

(3)Handler造成內(nèi)存泄漏分析

當(dāng)Android應(yīng)用程序啟動時,F(xiàn)ramework會為該應(yīng)用程序的主線程創(chuàng)建一個Looper對象。這個Looper對象包含一個簡單的消息隊列Message Queue,并且能夠循環(huán)的處理隊列中的消息。這些消息包括大多數(shù)應(yīng)用程序Framework事件,例如Activity生命周期方法調(diào)用、點擊事件等,這些消息都會被添加到消息隊列中并被逐個處理。另外,主線程的Looper對象會伴隨該應(yīng)用程序的整個生命周期。

當(dāng)在主線程中初始化Handler時,該Handler就會自動和主線程Looper的消息隊列關(guān)聯(lián)起來。所有發(fā)送到消息隊列的消息Message都會擁有一個對Handler的引用,所以當(dāng)Looper來處理消息時,會據(jù)此回調(diào)Handler的handleMessage(Message)方法來分發(fā)處理該消息。

在Java里,非靜態(tài)內(nèi)部類和匿名內(nèi)部類都會潛在的引用它們所屬的外部類。但是,靜態(tài)內(nèi)部類不會引用外部類對象。

當(dāng)使用內(nèi)部類(包括匿名內(nèi)部類)來創(chuàng)建Handler的時候,Handler對象會持有外部類對象(通常是一個Activity)的引用(不然怎么通過Handler來操作Activity中的View?)。而Handler通常會伴隨著一個耗時的后臺線程(例如從網(wǎng)絡(luò)拉取圖片)一起出現(xiàn),這個后臺線程在任務(wù)執(zhí)行完畢(例如圖片下載完畢)之后,通過消息機(jī)制通知Handler,然后Handler把圖片更新到界面。然而,如果用戶在網(wǎng)絡(luò)請求過程中關(guān)閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由于這時線程尚未執(zhí)行完,而該線程持有Handler的引用(不然它怎么發(fā)消息給Handler?),這個Handler又持有Activity的引用,就導(dǎo)致該Activity無法被回收(即內(nèi)存泄露),直到網(wǎng)絡(luò)請求結(jié)束(例如圖片下載完畢)。

如果執(zhí)行了Handler的postDelayed()方法,該方法會將Handler裝入一個Message,并把這條Message推到MessageQueue中,那么在你設(shè)定的delay到達(dá)之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導(dǎo)致Activity被持有引用而無法被GC回收。

(4)內(nèi)存泄漏的危害

虛擬機(jī)占用內(nèi)存過高,導(dǎo)致OOM(內(nèi)存溢出),程序出錯。對于Android應(yīng)用來說,就是用戶打開一個Activity,使用完之后關(guān)閉它,內(nèi)存泄露;又打開,又關(guān)閉,又泄露;幾次之后,程序占用內(nèi)存超過系統(tǒng)限制。

3.Handler導(dǎo)致內(nèi)存泄漏解決方法

方法一:通過程序邏輯進(jìn)行保護(hù)

1.在關(guān)閉Activity的時候停掉后臺線程。線程停掉了,就相當(dāng)于切斷了Handler和外部連接的線,Activity自然會在合適的時候被GC回收。

2.如果Handler是被delay的Message持有了引用,那么使用Handler的removeCallbacks()方法,把消息對象從消息隊列移除就行了。

方法二:將Handler聲明為靜態(tài)類

靜態(tài)類不持有外部類的對象,所以Activity可以隨意被回收。代碼如下:

private static class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
}

但其實沒這么簡單。使用了以上代碼之后,你會發(fā)現(xiàn),由于Handler不再持有外部類對象的引用,導(dǎo)致程序不允許你在Handler中操作Activity中的對象了。所以你需要在Handler中增加一個對Activity的弱引用(WeakReference):

private final MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
    private final WeakReference<Activity> mActivity;
    public MyHandler(Activity activity) {
      mActivity = new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      final Activity activity = mActivity.get();
      if (activity != null) {
        // doSomething
      }
    }
  }

對于匿名類Runnable,同樣可以將其設(shè)置為靜態(tài)類:

private static final Runnable mRunnable = new Runnable() {
      @Override
      public void run() { 
          // doSomething
      }
  };

4.什么是WeakReference?

WeakReference弱引用,與強(qiáng)引用(即我們常說的引用)相對,它的特點是,GC在回收時會忽略掉弱引用,即就算有弱引用指向某對象,但只要該對象沒有被強(qiáng)引用指向(實際上多數(shù)時候還要求沒有軟引用,但此處軟引用的概念可以忽略),該對象就會在被GC檢查到時回收掉。對于上面的代碼,用戶在關(guān)閉Activity之后,就算后臺線程還沒結(jié)束,但由于僅有一條來自Handler的弱引用指向Activity,所以GC仍然會在檢查的時候把Activity回收掉。這樣,內(nèi)存泄露的問題就不會出現(xiàn)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容