至少在T-Mobile G1上Android應用在堆上分配的記憶體大小被限制16MB以內。對於手機來說,這是個不小的記憶體,但是這仍然遠遠不能滿足一些開發人員的需求。但是,即使你不打算使用所有的記憶體空間,你也應該儘可能地少用記憶體,從而使得其他應用能夠運行而不是被殺掉。因為Android能夠在記憶體中保持的應用越多,那麼使用者切換應用的速度就會越快。作為我工作的一部分,我在做android應用開發的時候也會陷入記憶體流失的問題中,大多數時候記憶體的泄漏都是由於犯了相同的錯誤:長期持有了一個Context的引用。
Android上 ,Context可以用於很多操作,但是大部分時候是用來載入以及使用資源。這就是為什麼所有的widgets在他們的建構函式中接受一個Context參數。在一般的android應用中,你通常有兩種Context:分別是Activity和Application。通常的,當我們的類和方法需要使用到context時,我們傳遞的是Activity這個context:
@Overrideprotected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); label.setText("Leaks are bad"); setContentView(label);}
這意味著views擁有一個對整個activity的引用,也就是引用了你的activity所擁有的一切;通常的,這指的是完整的視圖層級結構以及所有它的資源。因此,如果你泄露了一個Context(“ 泄漏 ”意味著你保持著它的一個引用,從而使它不能被記憶體回收機制回收),就意味著你泄漏了很多的記憶體。如果你不小心, 泄漏一 個activity的所有資源真的非常容易。
當 螢幕的方向發生改變的時候,系統預設將會銷毀當前的activity並且建立一個新的activity同時保持著原有的狀態。在做這個的時候,Android會從資源重新載入應用的UI。現在,想象一下你寫了一個應用,這個應用有一張很大的bitmap。你不想再每一次旋轉的時候重新載入它。最簡單的方法讓bitmap持續作用而不隨每一個方向而重新載入 ,就是把它放進一個靜態域:
private static Drawable sBackground; @Overrideprotected 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,這個在第一次螢幕改變時被建立的activity。當一個Drawable被關聯到一個view上,這個view就相當於在drawable上設定的一個回調。在上面的程式碼片段中,這表示drawable有一個TextView的引用,而這個TextView又擁有一個activity的引用(Context),activity依次引用了幾乎所有的東西(取決於你的代碼)。
這個例子展示了一個最簡單的Context 泄漏的情況,你可以在Home screen 的源碼中看到我們是如何解決這個問題的( 尋找unbindDrawables() 方法) ,這就是當activity 被銷毀的時候將drawables 的回調設為null 。有趣的是,你可能創造出一系列context泄漏的情況有很多,這非常糟糕。他們會是你很快記憶體溢出。
有兩種簡單的方法來避免context 相關的記憶體流失。最顯著地一個是避免context 逃出他自己的範圍之外。上面的例子就展示了使用靜態引用的情況,而內部類和他們對外部類的的隱式引用也是同樣危險的。第二種方法是使用Application context 。這個context 的生存周期和你的應用的生存周期一樣長,而不是取決於activity 的生存周期。如果你想保持一個長期生存的對象,並且這個對象需要一個context ,記得使用application 對象。你可以通過調用Context.getApplicationContext()
or Activity.getApplication() 來獲得。
總而言之,想要避免context 相關的記憶體流失 ,記住以下幾點:
· 不要對activity 的context 長期引用( 一個activity 的引用的生存周期應該和activity 的生命週期相同)
· 試著使用關於application的 context 來替代和activity相關的context
· 如果一個acitivity 的非靜態內部類的生命週期不受控制,那麼避免使用它;使用一個靜態內部類並且對其中的activity 使用一個弱引用。解決這個問題的方法是使用一個靜態內部類,並且對它的外部類有一WeakReference,就像在ViewRoot中內部類W所做的就是這麼個例子。
· 記憶體回收行程不能處理記憶體流失的保障。