單例模式的內存泄漏陷阱

(本篇博客舉了一個反面的例子,目的在於讓新手如何去發現本身的錯誤)app

最近項目開發中使用了一個叫作leakcanary的內存泄漏檢查工具,當開發中的調試運行時發生內存泄漏,leakcanary會在notification彈出一個內存泄漏報告,最近發生了個內存泄漏而且leakcanary給出了下列報告:函數

分析下Leakcanary給出的信息,最後一行它說PopOrderActivity這個實例發生了泄漏,即系統gc的時候沒有把這個activity給回收(本該回收的,應該是已經退出這個activity了),倒數第二行即說明了有一個叫作PendingOrderManager的類含有這個activity的引用,查看代碼,這個PendingOrderManager是個單例,同時它的構造函數傳入了一個Context參數:工具

public class PendingOrderManager {

    private static PendingOrderManager instance;

    private Context mContext;

    public PendingOrderManager(Context context) {
    
        this.mContext = context;
    }

    public static PendingOrderManager getInstance(Context context) {
        if (instance == null) {
            instance = new PendingOrderManager(context);
        }
        return instance;
    }

...

}

之因此要傳入個context是由於這個Manager裏面須要建立Preference。post

那麼如今發生內存泄漏的緣由也就很明瞭了,因爲PendingOrderManager是一個單例模式,那麼這個類的生命週期就伴隨整個應用的生命週期,而它在被PopOrderActivity建立的時候引用了PopOrderActivity,因此當系統GC的時候試圖去回收PopOrderActivity時,發現它卻在被另外一個任然在內存裏的PendingOrderManager所引用,因此GC回收它失敗,從而致使了內存泄漏。this

 

那麼如何解決這個問題呢?答案很簡單,在PendingOrderManager中對context的屬性使用弱引用便可:spa

public class PendingOrderManager {

    private static PendingOrderManager instance;

    private WeakReference<Context> wr;

    public PendingOrderManager(Context context) {
        L.d("PendingOrderManager <constructor>");
        wr = new WeakReference<>(context);
    
    }

    public static PendingOrderManager getInstance(Context context) {
        if (instance == null) {
            instance = new PendingOrderManager(context);
        }
        return instance;
    }

...
}

 

在PendingOrderManager中原來須要使用Context的地方,用wr.get()便可:.net

String timesListStr = (String) SPUtils.getPendingOrder(wr.get(), KEY_TIMES_LIST, "");
//這裏的wr.get()原來是mContext

 

這裏須要注意的一點是,因爲PendingOrderManager這個時候含有的「context」能夠被回收置空了,那麼後面使用context的地方要注意判斷是否爲空,即對wr.get的地方注意檢查空狀況。調試

還有一種方式能夠解決這個問題,考慮到每一個使用到PendingOrderManager的地方當都會經過這種方式:code

(PendingOrderManager.getInstance(mContext).getXXX()

即每次都能傳過來一個當前的調用者的context(確定不爲空),那麼在PendingOrderManager的getInstance方法裏面除了斷定instance是否爲空外,最好在斷定下wr.get是否爲空,這樣子若上一個實例化PendingOrderManager的activity被回收後,能夠考慮用新的context來從新建立PendingOrderManager的單例。改造後的getInstance方法:blog

public class PendingOrderManager {

    private static PendingOrderManager instance;

    private WeakReference<Context> wr;

    public PendingOrderManager(Context context) {
        L.d("PendingOrderManager <constructor>");
        wr = new WeakReference<>(context);
    
    }

    public static PendingOrderManager getInstance(Context context) {
        if (instance == null || wr.get() == null) {
            instance = new PendingOrderManager(context);
        }
        return instance;
    }

...
}

 

關於ApplicationContext

既然這個單例Manager是須要被全局訪問的,同時Manager裏面又須要context,那麼最好的方式就是用一個生命週期是整個app的context來代替。因此這個單例Manager並不須要構造的時候傳入一個context,只須要在Manager裏面使用context的地方經過getApplicationContext便可。由於application context的生命週期是最長的。

 

PS:

leakcanary是個很好的工具,下列是一些參考資料:

http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

相關文章
相關標籤/搜索