避免 Android 中 Context 引起的內存泄露

Context 在編寫Android程序時經常使用,意思爲上下文對象。

常用的有Activity的Context還是有Application的Context。Activity用來展示活動界面,包含了很多的視圖,而視圖又含有圖片,文字等資源。在Android中內存泄露很容易出現,而持有很多對象內存佔用的Activity更加容易出現內存泄露,開發者需要特別注意這個問題。

本文講介紹Android中Context,更具體的說是Activity內存泄露的情況,以及如何避免Activity內存泄露,加速應用性能。

Drawable引起的內存泄露

Drawable引起內存泄露這個問題是比較隱晦,難以察覺的。在閱讀了Romain Guy的Avoiding memory leaks,結合grepcode查看源碼才明白了。

在Android系統中,當我們進行了屏幕旋轉,默認情況下,會銷燬掉當前的Activity,並創建一個新的Activity並保持之前的狀態。在這個過程中,Android系統會重新加載程序的UI視圖和資源。假設我們有一個程序用到了一個很大的Bitmap圖像,我們不想每次屏幕旋轉時都重新加載這個Bitmap對象,最簡單的辦法就是將這個Bitmap對象使用static修飾。

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);

  TextView label = new TextView(this);
  label.setText("Leaks are bad");

  if (sBackground == null) {
    sBackground = getDrawable(R.drawable.large_bitmap);
  }
  label.setBackgroundDrawable(sBackground);

  setContentView(label);
}

但是上面的方法在屏幕旋轉時有可能引起內存泄露,無論是咋一看還是仔細看這段代碼,都很難發現哪裏引起了內存泄露。

當一個Drawable綁定到了View上,實際上這個View對象就會成爲這個Drawable的一個callback成員變量,上面的例子中靜態的sBackground持有TextView對象lable的引用,而lable只有Activity的引用,而Activity會持有其他更多對象的引用。sBackground生命週期要長於Activity。當屏幕旋轉時,Activity無法被銷燬,這樣就產生了內存泄露問題。

2.3.7及以下版本Drawable的setCallback方法的實現

public final void setCallback(Callback cb) {
    mCallback = cb;
}

好在從4.0.1開始,引入了弱引用處理這個問題,弱引用在GC回收時,不會阻止GC回收其指向的對象,避免了內存泄露問題。

public final void setCallback(Callback cb) {
    mCallback = new WeakReference<Callback>(cb);
}

單例引起的內存泄露

單例是我們比較簡單常用的一種設計模式,然而如果單例使用不當也會導致內存泄露。 比如這樣一個例子,我們使用餓漢式初始化單例,AppSettings我們需要持有一個Context作爲成員變量,如果我們按照下面的實現其實是有問題。

public class AppSettings {    
    private Context mAppContext;
    private static AppSettings sInstance = new AppSettings();

    //some other codes
    public static AppSettings getInstance() {
      return sInstance;
    }

    public final void setup(Context context) {
        mAppContext = context;
    }
}

sInstance作爲靜態對象,其生命週期要長於普通的對象,其中也包含Activity,當我們進行屏幕旋轉,默認情況下,系統會銷燬當前Activity,然後當前的Activity被一個單例持有,導致垃圾回收器無法進行回收,進而產生了內存泄露。

解決的方法就是不持有Activity的引用,而是持有Application的Context引用。代碼如下修改

public final void setup(Context context) {
    mAppContext = context.getApplicationContext(); 
}

訪問這裏瞭解更多關於單例模式的問題

條條方法返回Context

通常我們想要獲取Context對象,主要有以下四種方法

  • View.getContext,返回當前View對象的Context對象,通常是當前正在展示的Activity對象。
  • Activity.getApplicationContext,獲取當前Activity所在的(應用)進程的Context對象,通常我們使用Context對象時,要優先考慮這個全局的進程Context。
  • ContextWrapper.getBaseContext():用來獲取一個ContextWrapper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發中使用並不多,也不建議使用。
  • Activity.this 返回當前的Activity實例,如果是UI控件需要使用Activity作爲Context對象,但是默認的Toast實際上使用ApplicationContext也可以。

其他內存泄露問題

避免內存泄露須謹記

  • 不要讓生命週期長於Activity的對象持有到Activity的引用
  • 儘量使用Application的Context而不是Activity的Context
  • 儘量不要在Activity中使用非靜態內部類,因爲非靜態內部類會隱式持有外部類實例的引用(具體可以查看細話Java:”失效”的private修飾符瞭解)。如果使用靜態內部類,將外部實例引用作爲弱引用持有。
  • 垃圾回收不能解決內存泄露,瞭解Android中垃圾回收機制

參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章