6.1 內存機制及使用優化

6.1.1  Android的內存機制

  Android的程序由Java語言編寫,因此Android的內存管理與Java的內存管理類似。程序員經過new爲對象分配內存,全部對象在java堆內分配空間;然而對象的釋放是由垃圾回收器來完成的.java

  那麼GC怎麼可以確認某一個對象是否是已經被廢棄了呢?Java採用了有向圖的原理。Java將引用關係考慮爲圖的有向邊,有向邊從引用者指向引用對象。線程對象能夠做爲有向圖的起始頂點,該圖就是從起始頂點開始的一棵樹,根頂點能夠到達的對象都是有效對象,GC不會回收這些對象。若是某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),那麼咱們認爲這個對象再也不被引用,能夠被GC回收。android

  當系統內存不足時,系統將激活內存回收過程。爲了避免因內存回收影響用戶體驗(如殺死當前的活動進程),Android 基於進程中運行的組件及其狀態規定了默認的五個回收優先級,這幾種優先級的回收順序是 :程序員

Empty process、Background process、Service process、Visible process、Foreground process數據庫

  當 Android 應用程序退出時,並不清理其所佔用的內存,Linux 內核進程也相應的繼續存在,所謂「退出但不關閉」。從而使得用戶調用程序時可以在第一時間獲得響應。編程

6.1.2  Android的內存溢出

1. 內存泄露致使

  因爲咱們程序的失誤,長期保持某些資源(如Context)的引用,形成內存泄露,資源形成得不到釋放。緩存

常見的內存泄漏還有:網絡

1) 萬惡的static異步

   static是Java中的一個關鍵字,當用它來修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。ide

  如何纔能有效的避免這種引用的發生呢?函數

  •     第1、應該儘可能避免static成員變量引用資源耗費過多的實例,好比Context;
  •     第2、Context儘可能使用Application Context,它的生命週期比較長,引用它不會出現內存泄露的問題;
  •     第3、使用WeakReference代替強引用。好比可使用WeakReference mContextRef。

2) 線程惹的禍

  線程產生內存泄露的主要緣由在於線程生命週期的不可控。

  咱們來考慮下面一段代碼:

public class MyActivity extends Activity {           
  public void onCreate(Bundle savedInstanceState) {           
    super.onCreate(savedInstanceState);           
    setContentView(R.layout.main);           
    new MyThread().start();       
  }         
  private class MyThread extends Thread{           
    @Override           
    public void run() {               
      super.run();               
      // do somthing           
    }       
  }  
}

 

 咱們思考一個問題:假設MyThread的run函數是一個很費時的操做,當調用finish的時候Activity 會銷燬掉嗎?

  事實上因爲咱們的線程是Activity的內部類,因此MyThread中保存了Activity的一個引用,當MyThread的run函數沒有結束時,MyThread是不會被銷燬的,所以它所引用的老的Activity也不會被銷燬,所以就出現了內存泄露的問題。

解決方案: 將線程的內部類,改成靜態內部類。 若是須要引用Acitivity,使用弱引用

【附】內存泄漏調試:內存監測工具 DDMS --> Heap;內存分析工具 MAT(Memory Analyzer Tool)

2. 佔用內存較多的對象

  保存了多個耗用內存過大的對象(如Bitmap)或加載單個超大的圖片,形成內存超出限制。

1) 萬惡之源BitMap

  能夠說出現OutOfMemory問題的絕大多數人,都是由於Bitmap的問題。由於Bitmap佔用的內存實在是太多了,特別是分辨率大的圖片,若是要顯示多張那問題就更顯著了。

  解決方案:

  第一、及時的銷燬。

雖然,系統可以確認Bitmap分配的內存最終會被銷燬,可是因爲它佔用的內存過多,因此極可能會超過java堆的限制。所以,在用完Bitmap時,要及時的recycle掉。recycle並不能肯定當即就會將Bitmap釋放掉,可是會給虛擬機一個暗示:「該圖片能夠釋放了」。

  第2、設置必定的採樣率。

有時候,咱們要顯示的區域很小,沒有必要將整個圖片都加載出來,而只須要記載一個縮小過的圖片,這時候能夠設置必定的採樣率,那麼就能夠大大減少佔用的內存。以下面的代碼:

private ImageView preview;  
BitmapFactory.Options options = new BitmapFactory.Options();  
options.inSampleSize = 2;   //圖片寬高都爲原來的二分之一,即圖片爲原來的四分之一  
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);  
preview.setImageBitmap(bitmap); 

BitmapFactory這個類提供了多個解析方法用於建立Bitmap對象,好比:

    • SD卡中的圖片可使用decodeFile方法,
    • 網絡上的圖片可使用decodeStream方法,
    • 資源文件中的圖片可使用decodeResource方法。

這些方法會嘗試爲已經構建的bitmap分配內存,這時就會很容易致使OOM出現。

  爲此每一種解析方法都提供了一個可選的BitmapFactory.Options參數,將這個參數的inJustDecodeBounds屬性設置爲true就可讓解析方法禁止爲bitmap分配內存,返回值也再也不是一個Bitmap對象,而是null。

   雖然Bitmap是null了,可是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會被賦值。這個技巧讓咱們能夠在加載圖片以前就獲取到圖片的長寬值和MIME類型,從而根據狀況對圖片進行壓縮。如今圖片的大小已經知道了,咱們就能夠決定是把整張圖片加載到內存中仍是加載一個壓縮版的圖片到內存中。經過設置BitmapFactory.Options中inSampleSize的值就能夠實現。

  好比咱們有一張2048*1536像素的圖片,將inSampleSize的值設置爲4,就能夠把這張圖片壓縮成512*384像素。本來加載這張圖片須要佔用13M的內存,壓縮後就只須要佔用0.75M了(假設圖片是ARGB_8888類型,即每一個像素點佔用4個字節)。

  使用這個方法,首先你要將BitmapFactory.Options的inJustDecodeBounds屬性設置爲true,解析一次圖片。而後將BitmapFactory.Options連同指望的寬度和高度一塊兒傳遞到到calculateInSampleSize方法中,就能夠獲得合適的inSampleSize值了。以後再解析一次圖片,使用新獲取到的inSampleSize值,並把inJustDecodeBounds設置爲false,就能夠獲得壓縮後的圖片了。

  第3、巧妙的運用軟引用(SoftRefrence

    有些時候,咱們使用Bitmap後沒有保留對它的引用,所以就沒法調用Recycle函數。這時候巧妙的運用軟引用,可使Bitmap在內存快不足時獲得有效的釋放。

  第4、異步加載和緩存 ,如使用第三方加載庫。

 2) 行蹤詭異的Cursor

Cursor是Android查詢數據後獲得的一個管理數據集合的類,正常狀況下,若是查詢獲得的數據量較小時不會有內存問題,並且虛擬機可以保證Cusor最終會被釋放掉。

然而若是Cursor的數據量特表大,特別是若是裏面有Blob信息時,應該保證Cursor佔用的內存被及時的釋放掉,而不是等待GC來處理。而且Android明顯是傾向於編程者手動的將Cursor close掉並且android數據庫中對Cursor資源的是又限制個數的,若是不及時close掉,會致使別的地方沒法得到

3) 構造Adapter時,沒有使用緩存的 convertView

    以構造ListView的BaseAdapter爲例,在BaseAdapter中提供了方法: getView( )

   AdapterView 在使用View會有一個循環的View隊列的,把不顯示的View從新投入使用,因此在convertView不爲空的時候,不要直接建立新的View。

相關文章
相關標籤/搜索