單例模式的內存泄漏陷阱


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

最近項目開發中使用了一個叫做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。

那么現在發生內存泄漏的原因也就很明了了,由於PendingOrderManager是一個單例模式,那么這個類的生命周期就伴隨整個應用的生命周期,而它在被PopOrderActivity創建的時候引用了PopOrderActivity,所以當系統GC的時候試圖去回收PopOrderActivity時,發現它卻在被另一個任然在內存里的PendingOrderManager所引用,所以GC回收它失敗,從而導致了內存泄漏。

 

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

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()即可:

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

 

這里需要注意的一點是,由於PendingOrderManager這個時候含有的“context”可以被回收置空了,那么后面使用context的地方要注意判斷是否為空,即對wr.get的地方注意檢查空情況。

還有一種方式可以解決這個問題,考慮到每個使用到PendingOrderManager的地方當都會通過這種方式:

(PendingOrderManager.getInstance(mContext).getXXX()

即每次都能傳過來一個當前的調用者的context(肯定不為空),那么在PendingOrderManager的getInstance方法里面除了判定instance是否為空外,最好在判定下wr.get是否為空,這樣子若上一個實例化PendingOrderManager的activity被回收后,可以考慮用新的context來重新創建PendingOrderManager的單例。改造后的getInstance方法:

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/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM