Android的應用被限制為最多佔用16m的記憶體,至少在T-Mobile G1上是這樣的(當然現在已經有幾百兆的記憶體可以用了——譯者注)。它包括電話本身佔用的和開發人員可以使用的兩部分。即使你沒有佔用全部記憶體的打算,你也應該盡量少的使用記憶體,以免別的應用在啟動並執行時候關閉你的應用。Android能在記憶體中保持的應用越多,使用者在切換應用的時候就越快。作為我的一項工作,我仔細研究了Android應用的記憶體泄露問題,大多數情況下它們是由同一個錯誤引起的,那就是對一個上下文(Context)保持了長時間的引用。
在Android中,上下文(Context)被用作很多操作中,但是大部分是載入和訪問資源。這就是所有的widget都會在它們的建構函式中接受一個上下文(Context)參數。在一個合格的Android應用中,你通常能夠用到兩種上下文(Context):活動(Activity)和應用(Application)。活動(Activity)通常被傳遞給需要上下文(Context)參數的類或者方法:
- @Override
- protected void onCreate(Bundle state) {
- super.onCreate(state);
-
- TextView label = new TextView(this);
- label.setText("Leaks are bad");
-
- setContentView(label);
- }
這就意味著那個View有一個對整個活動(Activity)的引用並且對這個活動(Activity)中保持的所有對象有保持了引用;通常它們包括整個View的層次和它的所有資源。因此,如果你“泄露”了上下文(Context)(這裡“泄露”的意思是你保持了一個引用並且組織GC收集它),你將造成大量的記憶體泄露。如果你不夠小心的話,“泄露”一整個活動(Activity)是件非常簡單的事情。
當螢幕的方向改變時系統會預設的銷毀當前的活動(Activity)並且建立一個新的並且保持了它的狀態。這樣的結果就是Android會從資源中重新載入應用的UI。現在想象一下,你寫了一個應用,有一個非常大的位元影像,並且你並不想在每次旋轉時都重新載入。保留它並且每次旋轉不重新載入的最簡單的辦法就是把它儲存在一個靜態欄位上:
- 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);
- }
這段代碼非常快,同時也錯的夠離譜。它泄露了當第一次螢幕角度改變時建立的第一個活動(Activity)。當一個Drawable被附加到一個View,這個View被設定為drawable的一個回調。在上面的代碼片斷中,這意味著這個Drawable對TextView有一個引用,同時這個TextView對Activity(Context對象)保持著引用,同時這個Activity對很多個物件又有引用(這個多少還要看你的代碼了)。
這個例子是造成Context泄露的最簡單的一個原因,你可以看一下我們在主畫面源碼(查看unbindDrawables()方法)中是通過在Activity銷毀時設定儲存過的Drawable的回調為空白來解決這個問題的。更為有趣的是,你可以建立一個context泄露的鏈,當然這非常的糟糕。它們可以讓你飛快的用光所有的記憶體。
有兩種簡單的方法可以避免與context相關的記憶體泄露。最明顯的一個就是避免在context的自身的範圍外使用它。上面的例子展示了在類內部的一個靜態引用和它們對外部類的間接引用是非常危險的。第二個解決方案就是使用Application Context。這個context會伴隨你的應用而存在,並且不依賴Activity的的生命週期。如果你計劃保持一個需要context的長生命週期的對象,請記得考慮Application對象。你可以非常方便的通過調用Context.getApplicationContext() 或者 Activity.getApplication()擷取它。
總之,為了避免涉及到context的記憶體泄露,請記住如下幾點:
- 不要對一個Activity Context保持長生命週期的引用(一個對Activity的引用應該與Activity自身的生命週期相同)
- 嘗試使用應用上下文(context-application)代替活動上下文(context-activity)
- 如果你不能控制它們的生命週期,在活動(Activity)中避免使用不是靜態內部類,使用靜態類並且使用弱引用到活動(Activity)的內部。對於這個問題的解決方案是使用靜態內部類與一個弱引用(WeakReference)的外部類。就像ViewRoot和它的W內部類那麼實現的。
- 記憶體回收行程對於記憶體泄露來說並不是百分百保險的。
原文地址:Avoiding memory leaks