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

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

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

Java內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒有使用價(jià)值了,但是它們卻可以直接或間接地引用到導(dǎo)致無法被GC回收。無用的對(duì)象占據(jù)著內(nèi)存空間,使得實(shí)際可使用內(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時(shí),這是一段很常見的代碼。但是,它卻會(huì)造成嚴(yán)重的內(nèi)存泄漏問題。在實(shí)際編寫中,我們往往會(huì)得到如下警告:

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

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

class OuterClass { 
    class InnerClass{ 
    }
}

以上代碼mHandler讓人并不覺得是內(nèi)部類,它并不像InnerClass那樣形象,但是其實(shí)以下句柄實(shí)現(xiàn)一個(gè)繼承Handler的類,也就是自定義了一個(gè)類,那么明顯它就是一個(gè)內(nèi)部類。其實(shí)它是屬于內(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)用程序啟動(dòng)時(shí),F(xiàn)ramework會(huì)為該應(yīng)用程序的主線程創(chuàng)建一個(gè)Looper對(duì)象。這個(gè)Looper對(duì)象包含一個(gè)簡(jiǎn)單的消息隊(duì)列Message Queue,并且能夠循環(huán)的處理隊(duì)列中的消息。這些消息包括大多數(shù)應(yīng)用程序Framework事件,例如Activity生命周期方法調(diào)用、點(diǎn)擊事件等,這些消息都會(huì)被添加到消息隊(duì)列中并被逐個(gè)處理。另外,主線程的Looper對(duì)象會(huì)伴隨該應(yīng)用程序的整個(gè)生命周期。

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

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

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

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

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

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

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

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

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

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

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

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

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

但其實(shí)沒這么簡(jiǎn)單。使用了以上代碼之后,你會(huì)發(fā)現(xiàn),由于Handler不再持有外部類對(duì)象的引用,導(dǎo)致程序不允許你在Handler中操作Activity中的對(duì)象了。所以你需要在Handler中增加一個(gè)對(duì)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
      }
    }
  }

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

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

4.什么是WeakReference?

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容