1. 程式人生 > >(轉)Context記憶體洩漏問題

(轉)Context記憶體洩漏問題

今天看了一篇介紹Context的文章,寫的不錯,裡面有一段內容平時開發時候沒有注意到,摘抄如下:
在專案中,我們經常會遇到使用單例模式或者靜態static變數,雖然使用靜態類或者靜態變數很方便,但是也潛在很多的記憶體洩漏問題。

6.1靜態資源導致的記憶體洩漏

你可能遇到以下這段程式碼:

public class MyCustomResource {
    //靜態變數drawable
    private static Drawable drawable;

    public MyCustomResource(Context context) {
        Resources resources = context.getResources();
        drawable = resources.getDrawable(R.drawable.ic_launcher);
    }
}

請問,這段程式碼有什麼問題?乍一看貌似沒啥問題,挺好的啊!其實不然,主要的問題在於靜態變數drawable這裡,我們知道靜態變數在整個應用的記憶體裡只儲存一份,一旦建立就不會釋放該變數的記憶體,直到整個應用都銷燬才會釋放static靜態變數的記憶體。可是以上drawable資源是由Context物件來獲得,從而靜態變數drawable會持有該Context物件的引用,也就意味著如果該Context對應的Activity退出finish掉的時候其實該Activity是不能完全釋放記憶體的,因為靜態變數drawable持有該Activity的Context。從而導致該Activity記憶體無法回收,導致記憶體洩漏隱患。因為Activity就是Context,所有Context的生命週期和Activity是一樣長的,我們希望Activity退出時Context也釋放記憶體,這樣才不會導致記憶體洩漏隱患。那麼以上這段程式碼是不安全的,如果程式碼必須要使用靜態資源怎麼辦呢?其實我們可以這麼修改:

public class MyCustomResource {
    //靜態變數drawable
    private static Drawable drawable;

    public MyCustomResource(Context context) {
        Resources resources = (context.getApplicationContext()).getResources();
        drawable = resources.getDrawable(R.drawable.ic_launcher);
    }

}

這裡我們修改成了使用getApplicationContext去獲取App的資源。由上一小節可知getApplicationContext返回的物件是Application的Context,而Application的生命週期和整個應用是一樣的,應用啟動Application被建立,整個應用退出Application銷燬。所以Application的Context的生命週期就是整個應用的生命週期,恰好可以用來獲取靜態資源。利用Application的Context就不會導致記憶體洩漏了。

6.2 單例模式導致記憶體洩漏

相信單例模式對開發者很有誘惑力吧!或多或少在專案中都有用過單例模式。你也可能見過一下這段程式碼:

public class CustomManager {
    private static CustomManager sInstance;
    public static CustomManager getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new CustomManager(context);
        }
        return sInstance;
    }

    private Context mContext;
    private CustomManager(Context context) {
        mContext = context;
    }
}

同樣,以上程式碼也存在記憶體洩漏的隱患。因為單例模式使用的是靜態類的方式,讓該物件在整個應用的記憶體中保持一份該物件,從而減少對多次建立物件帶來的資源浪費。同樣的問題:在建立該單例的時候使用了生命週期端的Context物件的引用,如果你是在Application中建立以上單例的話是木有任何問題的。因為Application的Context生命週期是整個應用,和單例的生命週期一樣,因此不會導致記憶體洩漏。但是,如果你是在Activity中建立以上單例的話,就會導致和6.1小節一樣的問題—記憶體洩漏。所以我們同樣可以將程式碼修改成如下:

public class CustomManager {
    private static CustomManager sInstance;
    public static CustomManager getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new CustomManager(context.getApplicationContext());
        }
        return sInstance;
    }

    private Context mContext;
    private CustomManager(Context context) {
        mContext = context;
    }
}

6.3 總結

以後在使用Context物件獲取靜態資源,建立單例物件或者靜態方法的時候,請多考慮Context的生命週期,一定要記得不要使用Activity的Context,切記要使用生命週期長的Application的Context物件。但是並不是所有情況使用Application的Context物件,比如第4小節,在建立Dialog,View控制元件的時候都必須使用Activity的Context物件。