(本篇博客舉了一個反面的例子,目的在於讓新手如何去發現本身的錯誤)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是個很好的工具,下列是一些參考資料: