「內存泄漏」就是一個對象已經不須要再使用了,可是由於其它的對象持有該對象的引用,致使它的內存不能被回收。「內存泄漏」的慢慢積累,最終會致使OOM 「內存溢出」的發生,千里之堤,毀於蟻穴。因此在寫代碼的過程當中,應該要注意規避會致使「內存泄漏」的代碼寫法,提升軟件的健壯性。java
1、常見致使「內存泄漏」的代碼寫法及解決方案android
1.靜態變量引發的內存泄漏數組
在java中靜態變量的生命週期是在類加載時開始,類卸載時結束。換句話說,在android中其生命週期是在進程啓動時開始,進程死亡時結束。因此在程序的運行期間,若是進程沒有被殺死,靜態變量就會一直存在,不會被回收掉。若是靜態變量強引用了某個Activity中變量,那麼這個Activity就一樣也不會被釋放,即使是該Activity執行了onDestroy(不要將執行onDestroy和被回收劃等號)。緩存
這類問題的解決方案爲:1.尋找與該靜態變量生命週期差很少的替代對象。2.若找不到,將強引用方式改爲弱引用。ide
比較典型的例子以下:
單例引發的Context內存泄漏佈局
public class IMManager { private Context context; private static IMManager mInstance; public static IMManager getInstance(Context context) { if (mInstance == null) { synchronized (IMManager.class) { if (mInstance == null) mInstance = new IMManager(context); } } return mInstance; } private IMManager(Context context) { this.context = context; } }
當調用getInstance時,若是傳入的context是Activity的context。只要這個單例沒有被釋放,這個Activity也不會被釋放。fetch
解決方案this
傳入Application的context,由於Application的context的生命週期比Activity長,能夠理解爲Application的context與單例的生命週期同樣長,傳入它是最合適的。spa
public class IMManager { private Context context; private static IMManager mInstance; public static IMManager getInstance(Context context) { if (mInstance == null) { synchronized (IMManager.class) { if (mInstance == null) //將傳入的context轉換成Application的context mInstance = new IMManager(context.getApplicationContext()); } } return mInstance; } private IMManager(Context context) { this.context = context; } }
二、非靜態內部類引發的內存泄漏線程
在java中,建立一個非靜態的內部類實例,就會引用它的外圍實例。若是這個非靜態內部類實例作了一些耗時的操做,就會形成外圍對象不會被回收,從而致使內存泄漏。
這類問題的解決方案爲:1.將內部類變成靜態內部類 2.若是有強引用Activity中的屬性,則將該屬性的引用方式改成弱引用。3.在業務容許的狀況下,當Activity執行onDestory時,結束這些耗時任務。
內部線程形成的內存泄漏
public class LeakAty extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } public void test() { //匿名內部類會引用其外圍實例LeakAty.this,因此會致使內存泄漏 new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
解決方案
將非靜態匿名內部類修改成靜態匿名內部類
public class LeakAty extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } //加上static,變成靜態匿名內部類 public static void test() { new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
Handler引發的內存泄漏
public class LeakAty extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); fetchData(); } private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case 0: // 刷新數據 break; default: break; } }; }; private void fetchData() { //獲取數據 mHandler.sendEmptyMessage(0); } }
mHandler 爲匿名內部類實例,會引用外圍對象LeakAty.this,若是該Handler在Activity退出時依然還有消息須要處理,那麼這個Activity就不會被回收。
解決方案
public class LeakAty extends Activity { private TextView tvResult; private MyHandler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); tvResult = (TextView) findViewById(R.id.tvResult); handler = new MyHandler(this); fetchData(); } //第一步,將Handler改爲靜態內部類。 private static class MyHandler extends Handler { //第二步,將須要引用Activity的地方,改爲弱引用。 private WeakReference<LeakAty> atyInstance; public MyHandler(LeakAty aty) { this.atyInstance = new WeakReference<LeakAty>(aty); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); LeakAty aty = atyInstance == null ? null : atyInstance.get(); //若是Activity被釋放回收了,則不處理這些消息 if (aty == null||aty.isFinishing()) { return; } aty.tvResult.setText("fetch data success"); } } private void fetchData() { // 獲取數據 handler.sendEmptyMessage(0); } @Override protected void onDestroy() { //第三步,在Activity退出的時候移除回調 super.onDestroy(); handler.removeCallbacksAndMessages(null); } }
三、資源未關閉引發的內存泄漏
當使用了BraodcastReceiver、File、Cursor、Bitmap等資源時,當不須要使用時,須要及時釋放掉,若沒有釋放,則會引發內存泄漏。
四、構造Adapter時,沒有使用緩存的 convertView
以構造ListView的BaseAdapter爲例,在BaseAdapter中提共了方法:
public View getView(intposition, View convertView, ViewGroup parent)
來向ListView提供每個item所須要的view對象。初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化必定數量的view對象,同時ListView會將這些view對象緩存起來。當向上滾動ListView時,原先位於最上面的list item的view對象會被回收,而後被用來構造新出現的最下面的list item。這個構造過程就是由getView()方法完成的,getView()的第二個形參 View convertView就是被緩存起來的list item的view對象(初始化時緩存中沒有view對象則convertView是null)。
由此能夠看出,若是咱們不去使用convertView,而是每次都在getView()中從新實例化一個View對象的話,即浪費時間,也形成內存垃圾,給垃圾回收增長壓力,若是垃圾回收來不及的話,虛擬機將不得不給該應用進程分配更多的內存,形成沒必要要的內存開支。
五、一些不良代碼成內存壓力
有些代碼並不形成內存泄露,可是它們或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存,對內存的回收和分配形成很大影響的,容易迫使虛擬機不得不給該應用進程分配更多的內存,增長vm的負擔,形成沒必要要的內存開支。
如Bitmap使用不當
第1、及時的銷燬。
雖然,系統可以確認Bitmap分配的內存最終會被銷燬,可是因爲它佔用的內存過多,因此極可能會超過Java堆的限制。所以,在用完Bitmap時,要及時的recycle掉。recycle並不能肯定當即就會將Bitmap釋放掉,可是會給虛擬機一個暗示:「該圖片能夠釋放了」。
第2、設置必定的採樣率。
有時候,咱們要顯示的區域很小,沒有必要將整個圖片都加載出來,而只須要記載一個縮小過的圖片,這時候能夠設置必定的採樣率,那麼就能夠大大減少佔用的內存。以下面的代碼:
private ImageView preview; BitmapFactory.Options options = newBitmapFactory.Options(); options.inSampleSize = 2;//圖片寬高都爲原來的二分之一,即圖片爲原來的四分之一 Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); preview.setImageBitmap(bitmap);
總結