Android避免內存泄露:合理使用getContext()和getApplication()

Android應用程序限制使用的堆內存是16M(注:堆內存與設備的性能也有一定關係,性能高的設備可用堆內存可能是24M或者更高),其中電話功能會佔用一部分,而開發者能夠使用的則非常有限。如果你不打算用完所有內存,那麼你的應用就應該儘可能少用內存,從而使其他的程序在運行時不致於被殺掉。Android系統在內存中能夠持有的應用程序越多,用戶在程序間進行切換時就越快。作爲工作的一部分,我研究Android應用中的內存泄露問題,結果發現多數情況是同一個問題引起的:對Context持有一個過長的引用。
    在Android中,Context被用在許多操作中,但多數是爲了加載或訪問資源。這就是爲什麼所有的Widget組件都會在構造器中接收一個Context參數的原因所在。對於一般的Android應用,通常有兩種Context,即ActivityApplication。開發者通常需要context在類或方法間傳遞時會選擇Activity。


@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);
    TextView label = new TextView(this);
    label.setText("Leaks are bad");
    setContentView(label);
}
這表明Views對整個Activity有一個引用,因此你的Activity中任何東西都會被(Views)持有,一般被持有的是視圖層級和它對應的資源。因此,如果你泄露了Context(泄露:即你持有一個Context引用,結果阻止GC對其進行回收),你就泄露了一些內存。如果你不細心,泄露一個Activity就會變得非常容易。

   當屏幕方向改變,系統默認會保存它的狀態,銷燬當前Activity並重新創建一個。爲了做到這一點,Android會從資源中重新加載應用程序的UI。現在假設你開發一個應用,包含了大量位圖(Bitmap),你不想每次屏幕切換時都去加載位圖。那麼最簡單的方法是利用static靜態域。

        private static Drawable sBackground;
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
                    
        TextView label = new TextView(this);
        label.setText("Leaks are bad");
                    
        if (sBackground == null) {
            sBackground = getDrawable(R.drawable.ic_launcher);
        }
        label.setBackground(sBackground);
                    
        setContentView(label);
    }


   上面的代碼非常簡潔,但也容易出錯:第一次屏幕方向改變時,最初創建的Activity(每次屏幕改變都會重新創建Activity)就會出現內存泄漏。當一個Drawable對象被附着到View中,View就會在這個Drawable上被設定作爲回調callback。上面的代碼片段表明drawable對TextView有一個引用,而TextView本身又對Activity(即Context)持有引用,反過來,Activity又對許多東西持有引用(這取決於你的代碼)。
   上面的例子是泄露Context最簡單的一種情況,你能在Home screen的源代碼(查找unbindDrawables()方法)中看到我們是怎麼解決它的:當Activity被銷燬時,設置被存儲的drawable的回調爲null。有趣的是,你創建一系列內存泄露的Context會出現許多情況,並且它們都是糟糕的。它們很快使你在運行過程時出現內存溢出。
   有兩種簡單的方式去避免Context相關的內存泄露。最明顯的一種就是避免Context超出它的範圍。上面的例子展現了靜態引用的情況:內部類以及對外部類的間接引用是很危險的。第二種方案是用Application。這種Context不依賴於Activity的生命週期,而是與你的應用程序同生共死。如果你打算持有一個長期活動並且需要Context引用的對象,記得使用Application對象。你可以通過調用Context.getApplicationContext() 或者 Activity.getApplication()方法得到它。
總的來說,要避免Context相關的內存泄露,銘記以下幾條:

不要對Activity(Activity繼承自Context)作長期的引用(一個指向Activity的引用與Activity本身有相同的生命週期);
試着用Application代替Activity;

如果你不能控制內部類的生命週期,避免使用非靜態內部類,應該用靜態內部類,並且對裏面的Activity作弱引用。該問題的解決方法是:對於外部類,用WeakReference構造靜態內部類,同時要在視圖根完成,並且它的WeakReference內部類要有一個實例(WeakReference)。

垃圾回收不是防止內存泄露的保險方式.



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