android內存優化

1、 Android的內存機制java

    Android的程序由Java語言編寫,因此Android的內存管理與Java的內存管理類似。程序員經過new爲對象分配內存,全部對象在java堆內分配空間;然而對象的釋放是由垃圾回收器來完成的。C/C++中的內存機制是「誰污染,誰治理」,java的就比較人性化了,給咱們請了一個專門的清潔工(GC)。程序員

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

2、Android的內存溢出數據庫

    Android的內存溢出是如何發生的?編程

    Android的虛擬機是基於寄存器的Dalvik,它的最大堆大小通常是16M,有的機器爲24M。所以咱們所能利用的內存空間是有限的。若是咱們的內存佔用超過了必定的水平就會出現OutOfMemory的錯誤。ide

爲何會出現內存不夠用的狀況呢?我想緣由主要有兩個:函數

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

  • 保存了多個耗用內存過大的對象(如Bitmap),形成內存超出限制。this

3、萬惡的staticspa

    static是Java中的一個關鍵字,當用它來修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。因此用static修飾的變量,它的生命週期是很長的,若是用它來引用一些資源耗費過多的實例(Context的狀況最多),這時就要謹慎對待了。

public class ClassName {       private static Context mContext;       //省略  }

以上的代碼是很危險的,若是將Activity賦值到麼mContext的話。那麼即便該Activity已經onDestroy,可是因爲仍有對象保存它的引用,所以該Activity依然不會被釋放。

    咱們舉Android文檔中的一個例子。

private static Drawable sBackground;        @Override   protected 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);   }

    sBackground, 是一個靜態的變量,可是咱們發現,咱們並無顯式的保存Contex的引用,可是,當Drawable與View鏈接以後,Drawable就將View設置爲一個回調,因爲View中是包含Context的引用的,因此,實際上咱們依然保存了Context的引用。這個引用鏈以下:

    Drawable->TextView->Context

    因此,最終該Context也沒有獲得釋放,發生了內存泄露。

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

    第一,應該儘可能避免static成員變量引用資源耗費過多的實例,好比Context。

    第2、Context儘可能使用Application Context,由於Application的Context的生命週期比較長,引用它不會出現內存泄露的問題。

    第3、使用WeakReference代替強引用。好比可使用WeakReference<Context> mContextRef;

    該部分的詳細內容也能夠參考Android文檔中Article部分。

4、都是線程惹的禍

    線程也是形成內存泄露的一個重要的源頭。線程產生內存泄露的主要緣由在於線程生命週期的不可控。咱們來考慮下面一段代碼。

public class MyActivity extends Activity {      @Override      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函數是一個很費時的操做,當咱們開啓該線程後,將設備的橫屏變爲了豎屏,通常狀況下當屏幕轉換時會從新建立Activity,按照咱們的想法,老的Activity應該會被銷燬纔對,然而事實上並不是如此。

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

 

    有些人喜歡用Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴重,Thread只有在run函數不結束時纔出現這種內存泄露問題,然而AsyncTask內部的實現機制是運用了ThreadPoolExcutor,該類產生的Thread對象的生命週期是不肯定的,是應用程序沒法控制的,所以若是AsyncTask做爲Activity的內部類,就更容易出現內存泄露的問題。

    這種線程致使的內存泄露問題應該如何解決呢?

    第1、將線程的內部類,改成靜態內部類。

    第2、在線程內部採用弱引用保存Context引用。

    解決的模型以下:

public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends          AsyncTask<Params, Progress, Result> {      protected WeakReference<WeakTarget> mTarget;        public WeakAsyncTask(WeakTarget target) {          mTarget = new WeakReference<WeakTarget>(target);      }        /** {@inheritDoc} */      @Override      protected final void onPreExecute() {          final WeakTarget target = mTarget.get();          if (target != null) {              this.onPreExecute(target);          }      }        /** {@inheritDoc} */      @Override      protected final Result doInBackground(Params... params) {          final WeakTarget target = mTarget.get();          if (target != null) {              return this.doInBackground(target, params);          } else {              return null;          }      }        /** {@inheritDoc} */      @Override      protected final void onPostExecute(Result result) {          final WeakTarget target = mTarget.get();          if (target != null) {              this.onPostExecute(target, result);          }      }        protected void onPreExecute(WeakTarget target) {          // No default action      }        protected abstract Result doInBackground(WeakTarget target, Params... params);        protected void onPostExecute(WeakTarget target, Result result) {          // No default action      }  }



    事實上,線程的問題並不只僅在於內存泄露,還會帶來一些災難性的問題。因爲本文討論的是內存問題,因此在此不作討論。

 

因爲51cto不讓我一次傳完,說個人字數太多了,因此分開傳了。 

5、超級大胖子Bitmap

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

    如何解決Bitmap帶給咱們的內存問題?

    第1、及時的銷燬。

    雖然,系統可以確認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);

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

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

/**本例子爲博主隨手一寫,來講明用法,並未驗證*/  private class MyAdapter extends BaseAdapter {        private ArrayList<SoftReference<Bitmap>> mBitmapRefs = new ArrayList<SoftReference<Bitmap>>();      private ArrayList<Value> mValues;      private Context mContext;      private LayoutInflater mInflater;        MyAdapter(Context context, ArrayList<Value> values) {          mContext = context;          mValues = values;          mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);      }      public int getCount() {          return mValues.size();      }        public Object getItem(int i) {          return mValues.get(i);      }        public long getItemId(int i) {          return i;      }        public View getView(int i, View view, ViewGroup viewGroup) {          View newView = null;          if(view != null) {              newView = view;          } else {              newView =(View)mInflater.inflate(R.layout.image_view, false);          }            Bitmap bitmap = BitmapFactory.decodeFile(mValues.get(i).fileName);          mBitmapRefs.add(new SoftReference<Bitmap>(bitmap));     //此處加入ArrayList          ((ImageView)newView).setImageBitmap(bitmap);            return newView;      }  }

6、行蹤詭異的Cursor

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

    然而若是Cursor的數據量特表大,特別是若是裏面有Blob信息時,應該保證Cursor佔用的內存被及時的釋放掉,而不是等待GC來處理。而且Android明顯是傾向於編程者手動的將Cursor close掉,由於在源代碼中咱們發現,若是等到垃圾回收器來回收時,會給用戶以錯誤提示。

    因此咱們使用Cursor的方式通常以下:

Cursor cursor = null;  try {      cursor = mContext.getContentResolver().query(uri,null, null,null,null);      if(cursor != null) {          cursor.moveToFirst();          //do something      }  } catch (Exception e) {      e.printStackTrace();    } finally {      if (cursor != null) {         cursor.close();      }  }

    有一種狀況下,咱們不能直接將Cursor關閉掉,這就是在CursorAdapter中應用的狀況,可是注意,CursorAdapter在Acivity結束時並無自動的將Cursor關閉掉,所以,你須要在onDestroy函數中,手動關閉。

@Override  protected void onDestroy() {            if (mAdapter != null && mAdapter.getCurosr() != null) {          mAdapter.getCursor().close();      }      super.onDestroy();   }

  CursorAdapter中的changeCursor函數,會將原來的Cursor釋放掉,並替換爲新的Cursor,因此你不用擔憂原來的Cursor沒有被關閉。

  你可能會想到使用Activity的managedQuery來生成Cursor,這樣Cursor就會與Acitivity的生命週期一致了,多麼完美的解決方法!然而事實上managedQuery也有很大的侷限性。

    managedQuery生成的Cursor必須確保不會被替換,由於可能不少程序事實上查詢條件都是不肯定的,所以咱們常常會用新查詢的Cursor來替換掉原先的Cursor。所以這種方法適用範圍也是很小。

7、其它要說的。

    其實,要減少內存的使用,其實還有不少方法和要求。好比不要使用整張整張的圖,儘可能使用9path圖片。Adapter要使用convertView等等,好多細節均可以節省內存。這些都須要咱們去挖掘,誰叫Android的內存不給力來着。

 

 

根據我我的的開發經驗總結了以下幾點優化內存的方法:

  1. 建立或其餘方式得到的對象如再也不使用,則主動將其置爲null。

  2. 儘可能在程序中少使用對圖片的放大或縮小或翻轉.在對圖片進行操做時佔用的內存可能比圖片自己要大一些。

  3. 儘量的將一些靜態的對象(尤爲是集合對象),放於SQLite數據庫中。而且對這些數據的搜索匹配儘量使用sql語句進行。

  4. 一些鏈接資源在不使用使應該釋放,如數據庫鏈接文件輸入輸出流等。應該避免在特殊的狀況下不釋放(如異常或其餘狀況)

  5. 一些長週期的對像引用了短週期的對象,可是這些短週期的對象可能只在很小的範圍內使用。因此在查內存中也應該清除這一隱患。

  6. 一個對象被多個對象引用,可是隻釋放了一處,也可能會致使這個對像不會被釋放。

相關文章
相關標籤/搜索