1、自定義 Handler 時如何有效地避免內存泄漏問題

分析:

在Android系統(tǒng)中,Handler是一個消息發(fā)送和處理機制的核心組件之一,與之配套的其他主要組件還有Looper和Message,MessageQueue。
Message和Runnable類是消息的載體。MessageQueue是消息等待的隊列。Looper則負責從隊列中取消息。

Handler有兩個主要作用:

1.安排調度(scheule)消息和可執(zhí)行的runnable,可以立即執(zhí)行,也可以安排在某個將來的時間點執(zhí)行。

2.讓某一個行為(action)在其他線程中執(zhí)行。

Handler是由系統(tǒng)所提供的一種異步消息處理的常用方式,一般情況下不會發(fā)生內存泄露。
Handler為什么可能造成內存泄漏。這里的內存泄漏,常常指的是泄漏了Activity等組件。

public class ShanActivity extends Activity{
    public Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
             
    }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    }
}

這有什么問題呢。問題在于該Handler的實例采用了內部類的寫法,它是ShanActivity這個實例的內部類,在Java中,關于內部類有一個特點:在java中,非靜態(tài)的內部類和匿名內部類都會隱式的持有一個外部類的引用。所以,該handler實例持有了ShanActivity的一個引用。

生命周期較長的組件引用了生命周期較短的組件。Handler就是一種典型的示例,以上面的代碼舉例。ShanActivity可能會被泄漏,也就是該組件沒有用了,比如調用了finish()后,垃圾回收器卻遲遲沒有回收該Activity。原因出在該實例的handler內部類引用了它,而該handler實例可能被MessageQueue引用著。

問題原因:

一般非靜態(tài)內部類持有外部類的引用的情況下,造成外部類在使用完成后不能被系統(tǒng)回收內存,從而造成內存泄漏。這里 Handler 持有外部類 Activity 的引用,而handler有又未處理完的message,一旦 Activity 被銷毀,而此時 Handler 依然持有 Activity 引用,就會造成內存泄漏。

解決方法:

1.保證Activity被finish()時該線程的消息隊列沒有這個Activity的handler內部類的引用。這個場景是及其常見的,因為handler經常被用來發(fā)延時消息。一個補救的辦法就是在該類需要回收的時候,手動地把消息隊列中的消息清空:mHandler.removeCallbacksAndMessages(null);

2.要么讓這個handler不持有Activity等外部組件實例,讓該Handler成為靜態(tài)內部類。(靜態(tài)內部類是不持有外部類的實例的,因而也就調用不了外部的實例方法了)

3.在2方法的基礎上,為了能調用外部的實例方法,傳遞一個外部的弱引用進來)

4.將Handler放到抽取出來放入一個單獨的頂層類文件中。

這里需要了解一下關于Java里面引用的知識:

強引用(Strong Reference) 默認引用。如果一個對象具有強引用,垃圾回收器絕不會回收它。在內存空 間不足時,Java虛擬機寧愿拋出OutOfMemory的錯誤,使程序異常終止,也不會強引用的對象來解決內存不足問題。
軟引用(SoftReference) 如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。
弱引用(WeakReference 在垃圾回收器一旦發(fā)現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。
虛引用(PhantomReference) 如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。

第三種,需要一些額外的代碼,比較通用。

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

@Override
public void handleMessage(Message msg) {
  ShanActivity activity = mActivity.get();
  if (activity != null) {
     //do Something
  }
}
}

第四種方式,抽取做單獨封裝。

/**
 * 實現回調弱引用的Handler
 * 防止由于內部持有導致的內存泄露
 * 傳入的Callback不能使用匿名實現的變量,必須與使用這個Handle的對象的生命周期一 
 * 致否則會被立即釋放掉了
 */
public class WeakRefHandler extends Handler {
    private WeakReference<Callback> mWeakReference;
    
    public WeakRefHandler(Callback callback) {
        mWeakReference = new WeakReference<Handler.Callback>(callback);
    }
    
    public WeakRefHandler(Callback callback, Looper looper) {
        super(looper);
        mWeakReference = new WeakReference<Handler.Callback>(callback);
    }
    
    @Override
    public void handleMessage(Message msg) {
        if (mWeakReference != null && mWeakReference.get() != null) {
            Callback callback = mWeakReference.get();
            callback.handleMessage(msg);
        }
    }
}

由于是弱引用,當該類需要被回收時,可以直接被回收掉。

WeakRefHandler的使用時如下:

    private Handler.Callback mCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch(msg.what){
            }
            return true;
        }
    };
    private Handler mHandler = new WeakRefHandler(mCallback);

總結:

handler改為弱引用不是一概而論(大家只考慮在activity問題,handler持有activity的引用),解決問題可以傳activity弱引用給handler就行,如果在service后臺用到handler,難道也弱引用?合理使用handler,要明白為什么泄漏,不是所有場景都能用弱引用

tip:

removeCallbacksAndMessages: remove所有message和runnable
removeCallbacks: 只remove message,無法remove runnable

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

友情鏈接更多精彩內容