本文是articles裡面的第一篇文章,其實老早就注意到這個問題,由於剛開始的時候沒有看懂,今天重新看了下,印象還是滿深刻的:它就是錯誤使用context導致記憶體泄露。
android系統的應用程式(至少T-Mobile G1)被限制16M堆大小範圍內。裝置擁有很多的記憶體但是開發人員想要得到卻很少。即使你不想使用裝置所有的記憶體,你也應該在不殺死其他應用程式的情況下使用最少(的記憶體)。越多的程式在記憶體中,使用者切換程式的速度就越快。在我的一部分工作中,我遇到一些記憶體問題他們大部分都源於一個錯誤:保持長時間引用Context(簡單說就是Context泄露)。
android系統裡,Context被常用來加在和使用資源。這也是為什麼很多Widget在夠找函數中接受一個Context的參數了。在一個通常的android應用裡,你經常可以使用兩個Context(Activity和Application)。開發人員通常會傳遞Context參數到一個類和一個方法中:
1 @Override
2 protected void onCreate(Bundle state) {
3 super.onCreate(state);
4
5 TextView label = new TextView(this);
6 label.setText("Leaks are bad");
7
8 setContentView(label);
9 }
把activity的context傳遞給view,意味著view擁有一個整個activity的引用,進而引用activity佔有的任何資源:通常整個View結構和所有的資源。因此,如果 context發生記憶體泄露的話,就會泄露很多記憶體。如果你不小心,你很容易泄露整個Activity。
當螢幕方法方向發生改變的時候,預設情況下,會銷毀當前的activity接著建立一個新的。那麼,android系統會重新加在應用的UI和資源。比如在一個應用裡,你不想每次旋轉的時候重新加在一張大的圖片,那麼最簡單的方式就是把它設定為靜態變數:
1 private static Drawable sBackground;
2
3 @Override
4 protected void onCreate(Bundle state) {
5 super.onCreate(state);
6
7 TextView label = new TextView(this);
8 label.setText("Leaks are bad");
9
10 if (sBackground == null) {
11 sBackground = getDrawable(R.drawable.large_bitmap);
12 }
13 label.setBackgroundDrawable(sBackground);
14
15 setContentView(label);
16 }
上述代碼十分錯誤。當第一個螢幕方向發生改變的時候,他會泄露第一個activity。當把圖片附在一個View的時候,Drawable的回掉函數callback()引用View(事實上在setBackgroundDrawable函數裡,發生sBackgroud.setCallBack(label))。在整個程式碼片段裡面,這個drawable對象使用者一個包含activity對象引用的TextView的引用。
這個範例是一個最簡單泄露Context的例子,你可以去Home screen's source code代碼裡面看看:當activity被銷毀的時候,設定被儲存drawable的callbacks為null.很有趣的是,許多你建立了一個鏈式的被泄露的Context是十分糟糕的。他們讓你相當快的消耗記憶體。
有兩種方式可以避免Context相關的記憶體泄露。最常用的一種做法就是避免在範圍外引用context.上面的例子就是顯示了錯誤例子。第二個解決辦法就是使用Application 的Context。這個Context的生命週期跟你的應用的生命週期一樣長,並且不依賴於activities的生命週期。如果你需要Context來儲存一個生命週期長的對象,記住使用application 的 context.你可以便於使用Context.getApplicationContext()或者Activity.getApplication()來得到。
總的來說,要避免Context相關的記憶體泄露,記住一下幾點:
1.不要讓生命週期長的對象引用activity 的context。(activity的引用應該和它本身有一樣的生命週期)
2.盡量使用application-context代替activity-context
3.如果你不能控制他們的生命週期,避免使用高非靜態內部類。使用靜態內部類,並在activity對它們弱引用。因為非靜態內部類的對象執行個體化時候預設包含了它的外部類的引用。
4.你的記憶體回收機制是否對記憶體泄露做了處理。(這點通常不考慮)