Android記憶體優化

zhuazhu發表於2018-03-24

首先我們來解釋下兩個名詞:

記憶體洩漏

當一個物件在程式執行過後已經不需要再使用了,但是有其他的物件還持有該物件的引用,以致該物件不能被GC回收,那麼這個物件會一直佔用記憶體,從而導致該記憶體不可用,這種本該被GC回收(不再需要用了)而又不能被回收(被其他物件持有引用),以致停留在堆記憶體中的物件就造成了記憶體洩露.

記憶體溢位

記憶體溢位(OutOfMeory),即我們通常所說的OOM,是指程式在申請記憶體時,沒有足夠的記憶體空間共其使用.

記憶體洩露的危害

  • 過多的記憶體洩露最終會導致記憶體溢位(OOM)
  • 記憶體洩露導致記憶體不足,會觸發頻繁GC,從而導致UI卡頓

常見記憶體洩露問題及解決方案

1.單例

  • 主要原因是因為一般情況下單列都是全域性的,有時候會引用一些實際生命週期比較短的變數,導致其無法釋放.
  • 解決方案:
// 使用了單例模式 
// 如果Context使用的是Activity的Context,則會造成記憶體溢位
//單例的Context 使用Application的Context,單例的生命週期和應用的一樣長,這樣基本可以防止單例引起來的記憶體洩露記憶體洩漏。
public class AppManager { 
    private static AppManager instance;  
    private Context context; private AppManager(Context context) { 
        this.context = context; 
     }  
    public static AppManager getInstance(Context context) {  
     if (instance != null) {  
         instance = new AppManager(context); 
      }  
      return instance;  
    }  
}
複製程式碼

2.非靜態內部類建立靜態例項造成的記憶體洩漏

public class MainActivity extends AppCompatActivity { 
    private static TestResource mResource = null;  

    @Override protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main);  
        if(mResource == null){  
            mResource = new TestResource(); 
         }  
         //...  
         }  
class TestResource { //... } }
複製程式碼
  • 非靜態內部類預設會持有外部類的引用,而改非靜態內部類有建立了一個靜態例項,該例項的生命週期和應用的一樣長,這就導致了該靜態例項一直會持有該Actvity的引用,從而導致Activity的記憶體資源不能被正常的回收.
  • 解決方案:將改內部類設為靜態內部類或者將該內部類抽取出來封裝成一個單列,如果需要使用Context,就使用Application的Context.

3.Handler造成的記憶體洩露

public class MainActivity extends AppCompatActivity {  
        private final Handler handler = new Handler() {   
        @Override public void handleMessage(Message msg) {  
        //  
        ... } 
        };  
         @Override protected void onCreate(Bundle savedInstanceState) {  
               super.onCreate(savedInstanceState);  
               setContentView(R.layout.activity_main); 
                new Thread(new Runnable() {  
                @Override public void run() { 
                 // ...  
                 handler.sendEmptyMessage(0x123);  
              } 
        });  
}
複製程式碼
  • 原因:當MainActivity結束時,未處理的訊息持有Handler的引用,而handler又持有他所屬的外部類也就是MainActivity,這條引用關係會一直保持知道訊息得到處理,這樣阻止了MainActivity內垃圾回收器回收,從而造成記憶體洩漏
  • 解決方案:將Handler類獨立出來或者使用靜態內部類,這樣便可以避免記憶體洩露

4.資源未關閉

  • 比如在Activity中register了一個BroadcastReceiver,但是Activity結束後沒有unregister該BroadcastReceiver
  • 資源性物件比如Cursor,Stream,File檔案等往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時關閉記憶體,它們的緩衝不僅存在於java虛擬機器內,還存在於java虛擬機器外,如果我們僅僅是把它的引用設定為null,而不關閉它們,往往會造成記憶體洩露.
  • 對於資源性物件在不使用的時候,應該呼叫他的close()函式將其關閉,然後再設定為null,在我們的程式退出時,一定要確保我們的資源性物件已經關閉
  • Bitmap物件不再使用的時候呼叫recycle(),2.3以後的bitmao應該不再需要手動recycle()了,記憶體已經在java層了

5.使用輕量的資料結構

  • 使用ArrayMap/SparseArray來代替HashMap,ArrayMap/SparseArray是專門為於東裝置設計的高效的資料結構

HashMap

  • HashMap內部使用了一個預設容量為16的陣列來儲存資料,採用拉鍊法解決hash衝突(資料+連結串列).
  • HashMap就算沒有資料,也需要分配預設16個元素的陣列,一旦資料量達到HashMap限定容量的75%,就將按兩倍擴容

SparseArray

  • 支援int型別,避免自動裝箱,但是也只支援int型別的key
  • 內部通過兩個資料來進行資料儲存,一個儲存key,另外一個儲存value
  • 因為key是int,在查詢時,採用二分查詢,效率高,SparseArray儲存的元素都是按元素的key值從小到大排列好的
  • 預設初四size為0,每次增加元素,size++

ArrayMap

  • 跟SparseArray一樣,內部兩個陣列,但是第一個存key的hash值,一個存value,物件按照key的hash值排序,二分查詢也是按照hash
  • 查詢index時,傳入key,計算出hash,通過二分查詢hash陣列,確定index

6.用StringDef和IntDef來代替列舉(Enum)

  • 列舉佔用的記憶體過大,google官方建議用註解StringDef和IntDef來替換列舉

7.Bitmap處理

  • 對Bitmap壓縮
  • Lru機制處理Bitmap
  • 使用有名的圖片快取框架(我一般使用這種)

8.不要使用String進行字串拼接

  • 嚴格的講,String拼接只能歸結到記憶體抖動中,因為產生的String副本能夠被GC,不會造成記憶體洩漏
  • 頻繁的字串拼接,使用StringBuffer或者StringBuiler代替String,可以在一定程度上避免OOM和記憶體抖動

9.謹慎使用static物件

  • static物件的生命週期過程,應該謹慎使用

相關文章