(本篇博客舉了一個反面的例子,目的在於讓新手如何去發現自己的錯誤)
最近項目開發中使用了一個叫做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/