Android Handler引起的內存泄漏分析與解決

  在開發中我們經常用Handler來進行子線程修改UI界面,進程間通訊等等,在用的過程中稍不注意就有可能發出handler leaks的警告,即“This Handler class should be static or leaks might occur ..”的警告。Google給出的解釋如下:

Issue: Ensures that Handler classes do not hold on to a reference to an outer class。(確保Hanler類不持有外部類的引用)
In Android, Handler classes should be static or leaks might occur. Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class。(在Android中, Handler類應是static的, 否則可能會發生泄漏. 程序線程的消息隊列的Message也持有對Handler對象的引用. 如果Handler是內部類, 那麼(回收內存時)其外部類也會被保留.(使用Handler的Service和Activity就也無法被回收. 這就可能導致內存泄露.) 爲了避免泄露其外部類, 可將Handler聲明爲static並持有其外部類的WeakReference(弱引用).
下面結合具體的例子來分析Handler在使用中造成內存泄露的原因。

1.Android中使用Handler造成內存泄露的原因

如果在Activity中定義了一個內部Handler類,如下代碼:

public class MainActivity extends Activity {    
   private  Handler mHandler = new Handler() {   
         @Override       
        public void handleMessage(Message msg) {       
              //TODO handle message...        
        }   
   };   
     @Override   
     public void onCreate(Bundle savedInstanceState) {   
           super.onCreate(savedInstanceState);  
           setContentView(R.layout.activity_main);  
           mHandler.sendMessageDelayed(Message.obtain(), 60000);                 
                 finish();  
   }
 }

這時會出現一個內存泄露警告:

This Handler class should be static or leaks might occur (com.example.ta.MainActivity.1)
Issue: Ensures that Handler classes do not hold on to a reference to an outer class
Id: HandlerLeak
In Android, Handler classes should be static or leaks might occur. Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class.

原因是這樣子的:
當Android應用啓動的時候,會先創建一個應用主線程的Looper對象,Looper實現了一個簡單的消息隊列,一個一個的處理裏面的Message對象。主線程Looper對象在整個應用生命週期中存在。當在主線程中初始化Handler時,該Handler和Looper的消息隊列關聯,發送到消息隊列的Message會引用發送該消息的Handler對象,這樣系統就可以調用 Handler#handleMessage(Message) 來分發處理該消息。然而,我們都知道在Java中,非靜態(匿名)內部類會引用外部類對象。而靜態內部類不會引用外部類對象。如果外部類是Activity,則會引起Activity泄露 。因爲當Activity finish後,延時消息會繼續存在主線程消息隊列中,然後處理消息。而該消息引用了Activity的Handler對象,然後這個Handler又引用了這個Activity。這些引用對象會保持到該消息被處理完,這樣就導致該Activity對象無法被回收,從而導致了上面說的 Activity泄露。也就是如果你執行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個Message,並把這條Message推到 MessageQueue中,那麼在你設定的delay到達之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導致你的Activity被持有引用而無法被回收。
另外,像這樣使用Handler來處理耗時的後臺任務,如下所示:

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

當使用內部類(包括匿名類)來創建Handler的時候,Handler對象會隱式地持有一個外部類對象(通常是一個Activity)的引用(不然你怎 麼可能通過Handler來操作Activity中的View?)。而Handler通常會伴隨着一個耗時的後臺線程(例如從網絡拉取圖片)一起出現,這 個後臺線程在任務執行完畢(例如圖片下載完畢)之後,通過消息機制通知Handler,然後Handler把圖片更新到界面。然而,如果用戶在網絡請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由於這時線程尚未執行完,而該線程持有 Handler的引用(不然它怎麼發消息給Handler?),這個Handler又持有Activity的引用,就導致該Activity無法被回收 (即內存泄露),直到網絡請求結束(例如圖片下載完畢)。
以上就是我覺得Android中使用Handler可能造成內存泄漏的原因。接下來分析解決方法。

2.Android中使用Handler導致內存泄露的解決方法

要修改該問題,只需要按照警告提示的那樣,把Handler類定義爲靜態即可,然後通過WeakReference 來保持外部的Activity對象。
(1)方法一:通過完善自己的代碼邏輯來進行保護。
1.在關閉Activity的時候停掉你的後臺線程。線程停掉了,就相當於切斷了Handler和外部連接的線,Activity自然會在合適的時候被回收。
2.如果你的Handler是被delay的Message持有了引用,那麼使用相應的Handler的removeCallbacks()方法,把消息對象從消息隊列移除就行了。
(2).將Handler聲明爲靜態類,然後通過WeakReference 來保持外部的Activity對象。
由於靜態類不持有外部類的對象,所以你的Activity可以隨意被回收。由於Handler不再持有外部類對象的引用,導致程序不允許你在Handler中操作Activity中的對象了。所以你需要在Handler中增加一個對Activity的弱引用(WeakReference)。
代碼如下:

private Handler 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) {   
          Log.i("Message"+msg);     
         if(mActivity.get() == null) {            
          return;        
        }    
   } 
}

如果是後臺耗時任務,也是這樣做:

static class MyHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}

綜上所述:
當我們在Activity中使用內部類的時候,需要時刻考慮是否可以控制該內部類的生命週期,如果不可以,則最好定義爲靜態內部類,以免造成內存泄漏。這是Android開發過程中經常被忽略掉的,特別是在開發自定義View組件的過程中經常忘記而導致內存泄漏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章