1. 程式人生 > >如何在Android中避免建立不必要的物件

如何在Android中避免建立不必要的物件

在程式設計開發中,記憶體的佔用是我們經常要面對的現實,通常的記憶體調優的方向就是儘量減少記憶體的佔用。這其中避免建立不必要的物件是一項重要的方面。

Android裝置不像PC那樣有著足夠大的記憶體,而且單個App佔用的記憶體實際上是比較小的。所以避免建立不必要的物件對於Android開發尤為重要。

本文會介紹一些常見的避免建立物件的場景和方法,其中有些屬於微優化,有的屬於編碼技巧,當然也有確實能夠起到顯著效果的方法。

使用單例

單例是我們常用的設計模式,使用這種模式,我們可以只提供一個物件供全域性呼叫。因此單例是避免建立不必要的物件的一種方式。

單例模式上手容易,但是需要注意很多問題,最重要的就是多執行緒併發的情況下保證單例的唯一性。當然方式很多,比如餓漢式,懶漢式double-check等。這裡介紹一個很極客的書寫單例的方式。

1
2
3
4
5
6
7
8
9
10
11
12
public class SingleInstance {
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      return SingleInstanceHolder.sInstance;
  }
  private static class SingleInstanceHolder {
      private static SingleInstance sInstance = new
SingleInstance();
} }

在Java中,類的靜態初始化會在類被載入時觸發,我們利用這個原理,可以實現利用這一特性,結合內部類,可以實現上面的程式碼,進行懶漢式建立例項。

關於單例,可以詳細參考文章單例這種設計模式

避免進行隱式裝箱

自動裝箱是Java 5 引入的一個特性,即自動將原始型別的資料轉換成對應的引用型別,比如將int轉為Integer等。

這種特性,極大的減少了編碼時的瑣碎工作,但是稍有不注意就可能建立了不必要的物件了。比如下面的程式碼

1
2
3
4
Integer sum
= 0;
for(int i=1000; i<5000; i++){ sum+=i; }

上面的程式碼sum+=i可以看成sum = sum + i,但是+這個操作符不適用於Integer物件,首先sum進行自動拆箱操作,進行數值相加操作,最後發生自動裝箱操作轉換成Integer物件。其內部變化如下

1
2
int result = sum.intValue() + i;
Integer sum = new Integer(result);

由於我們這裡宣告的sum為Integer型別,在上面的迴圈中會建立將近4000個無用的Integer物件,在這樣龐大的迴圈中,會降低程式的效能並且加重了垃圾回收的工作量。因此在我們程式設計時,需要注意到這一點,正確地宣告變數型別,避免因為自動裝箱引起的效能問題。

另外,當將原始資料型別的值加入集合中時,也會發生自動裝箱,所以這個過程中也是有物件建立的。如有需要避免這種情況,可以選擇SparseArray,SparseBooleanArray,SparseLongArray等容器。

關於Java中的自動裝箱與拆箱,參考文章Java中的自動裝箱與拆箱

謹慎選用容器

Java和Android提供了很多編輯的容器集合來組織物件。比如ArrayList,ContentValues,HashMap等。

然而,這樣容器雖然使用起來方便,但也存在一些問題,就是他們會自動擴容,這其中不是建立新的物件,而是建立一個更大的容器物件。這就意味這將佔用更大的記憶體空間。

以HashMap為例,當我們put key和value時,會檢測是否需要擴容,如需要則雙倍擴容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override public V put(K key, V value) {
        if (key == null) {
            return putValueForNullKey(value);
        }
        //some code here
        // No entry for (non-null) key is present; create one
        modCount++;
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        addNewEntry(key, value, hash, index);
        return null;
    }

關於擴容的問題,通常有如下幾種方法

  • 預估一個較大的容量值,避免多次擴容
  • 尋找替代的資料結構,確保做到時間和空間的平衡

用好LaunchMode

提到LaunchMode必然和Activity有關係。正常情況下我們在manifest中宣告Activity,如果不設定LaunchMode就使用預設的standard模式。

一旦設定成standard,每當有一次Intent請求,就會建立一個新的Activity例項。舉個例子,如果有10個撰寫郵件的Intent,那麼就會建立10個ComposeMailActivity的例項來處理這些Intent。結果很明顯,這種模式會建立某個Activity的多個例項。

如果對於一個搜尋功能的Activity,實際上保持一個Activity示例就可以了,使用standard模式會造成Activity例項的過多建立,因而不好。