Android之內存泄漏的發生與解決

一、什麼是內存泄露?

內存泄露指程序中沒有正確的釋放用完的內存空間,導致運行異常或錯誤

二、Android中內存泄露是如何發生的?

事實上Android中內存泄露很容易發生。最大的問題就是Context對象。

每 一個App都有一個全局的Application Context對象,可以通過getApplicationContext()方法獲得。每一個Activity都是Context的子類,會存儲有關當 前Activity的信息。很多情況下,內存泄露與Activity泄露有關。比如有的開發者會將Context對象在各個線程裏傳來傳去,寫一些靜態的 TextView,讓它們持有Activity的引用,這就是一個典型的錯誤。

Android的內存主要表現在:

1、 在Android平臺上,長期保持一些資源的引用,造成一些內存不能釋放,帶來的內存泄露問題很多。比如:Context(下文中提到的Activity都是Context),在一些你需要保持你的首個類對象狀態,並且把狀態傳入其他類對象中時,這樣消除掉首個類對象之前,你必須先把接收類對象釋放掉。需要注意一點的是:因爲在Java或者Android內存機制中,頂點的結點釋放前必須保證其他對象沒有調用才能被系統GC回收釋放。我們來看一段代碼:

@Override
protected void onCreate(Bundle state) {
     super.onCreate(state);
     TextViewlabel = new TextView(this);
     label.setText("Leaksare bad");
     setContentView(label);
}

代碼就是把一個TextView的實例加載到了我們正在運行的Activity(Context)當中,因此,通過GC回收機制,我們知道,要釋放Context,就必須先釋放掉引用他的一些對象。如果沒有,那在要釋放Context的時候,你會發現會有大量的內存溢出。所以在你不小心的情況下內存溢出是一件非常容易的事情。 保存一些對象時,同時也會造成內存泄露。最簡單的比如說位圖(Bitmap),比如說:在屏幕旋轉時,會破壞當前保持的一個Activity狀態,並且重新申請生成新的Activity,直到新的Activity狀態被保存。看一段代碼:

 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);
    }

這個代碼是非常快的同時也是錯誤的。它的內存泄露很容易出在屏幕轉移的方向上。雖然我們會發現沒有顯示的保存Context這個實例,但是當我們把繪製的圖連接到一個視圖的時候,Drawable就會將被View設置爲回調,這就說明,在上述的代碼中,其實在繪製TextView到活動中的時候,我們已經引用到了這個Activity。鏈接情況可以表現爲:Drawable->TextView->Context。

2、所以在想要釋放Context的時候,其實還是保存在內存中,並沒有得到釋放。

如何避免這種情況:主要在於。線程最容易出錯。大家不要小看線程,在Android裏面線程最容易造成內存泄露。線程產生內存泄露的主要原因在於線程生命週期的不可控。

3、在切換視圖屏幕的時候(橫豎屏),就會重新建立橫屏或者豎屏的Activity。我們形象的認爲之前建立的Activity會被回收,但是事實如何呢?Java機制不會給你同樣的感受,在我們釋放Activity之前,因爲run函數沒有結束,這樣MyThread並沒有銷燬,因此引用它的Activity(Mytest)也有沒有被銷燬,因此也帶來的內存泄露問題。

Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴重,Thread只有在run函數不結束時纔出現這種內存泄露問題,然而AsyncTask內部的實現機制是運用了ThreadPoolExcutor,該類產生的Thread對象的生命週期是不確定的,是應用程序無法控制的,因此如果AsyncTask作爲Activity的內部類,就更容易出現內存泄露的問題。

三、如何避免內存泄露?

1、不要將Context對象傳給activity與fragment以外的對象。

2、永遠不要將Context和View存儲在靜態變量中。

  private static TextView textView;//不用這種方式定義
  private static Context context;//不用這種方式定義

3、在onPause()/onDestroy()方法中解除監聽器,包括在Android自己的Listener,Location Service或Display Manager Service以及自己寫的Listener。

4、不要在後臺線程與AsyncTask中存儲activity的強引用。不然當Activity被關閉後,由於AsyncTask仍在執行且持有Activity的強引用,導致Activity無法被回收。

5、使用Application Context而不是Activity的Context

6、儘量不要用非靜態內部類,因爲它會持有外部類的引用。在非靜態內部類中存儲Activity或View的引用會導致內存泄露。如需存儲就使用WeakReference。

四、解決內存泄露

內存泄露使用MAT追蹤下篇文章說

在線程中問題的改進辦法主要有:

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

2、 在程序中儘量採用弱引用保存Context。

3、bitmap

Bitmap對象,對於一個內存對象,如果該對象所佔內存過大,在超出了系統的內存限制時候,內存泄露問題就很明顯了。解決bitmap主要是要解決在內存儘量不保存它或者使得採樣率變小。在很多時候,因爲我們的圖片像素很高,而對於手機屏幕尺寸來說我們並不用那麼高像素比例的圖片來加載時,先把圖片的採樣率降低在做原來的UI操作。在不需要保存bitmap對象的引用時候,我們還可以用軟引用來做替換。

1)及時回收bitmap內存:

回收bitmap內存可以用到以下代碼

if(bitmap != null && !bitmap.isRecycled()){  
        bitmap.recycle();  
        bitmap = null;  
}  
System.gc(); 

bitmap.recycle()方法用於回收該bitmap所佔用的內存,接着將bitmap置空,最後,別忘了用System.gc()調用一下系統的垃圾回收器。bitmap可以有多個(以爲着可以有多個if語句),但System.gc()最好只有一個(所以我將它寫在了if語句外),因爲System.gc(),每次調用都要將整個內存掃描一遍,因而如果多次調用的話會影響程序運行的速度。爲了程序的效率,我將它放在了所有回收語句之後,這樣已經起到了它的效果,還節約的時間。回收bitmap已經知道了,那麼“及時”怎麼理解呢?根據我的實際經驗,bitmap發揮作用的地方要麼在View裏,要麼在Activity裏(當然肯定有其他區域,但是原理都是類似的),回收bitmap的地方最好寫在這些區域剛剛不使用bitmap了的時刻。比如說View如果使用了bitmap,就應該在這個View不再繪製了的時候回收,或者是在跳轉到的下一個區域的代碼中回收;再比如說SurfaceView,就應該在onSurfaceDestroyed這個方法中回收;同理,如果Activity使用了bitmap,就可以在onStop或者onDestroy方法中回收......結合以上的共同點,“及時回收”的原理就是在使用了bitmap的區域結束時或結束後回收。

2)壓縮圖片:

使圖片體積大小變小,可以有兩種方式:

a、是使圖片質量降低(分辨率不變),

b、是使圖片分辨率降低(分辨率改變)。

總之,使圖片大小變小就行了。

五、總結:

第一:不要爲Context長期保存引用(要引用Context就要使得引用對象和它本身的生命週期保持一致)。

第二:如果要使用到Context,儘量使用ApplicationContext去代替Context,因爲ApplicationContext的生命週期較長,引用情況下不會造成內存泄露問題

第三:在你不控制對象的生命週期的情況下避免在你的Activity中使用static變量。儘量使用WeakReference去代替一個static。

第四:垃圾回收器並不保證能準確回收內存,這樣在使用自己需要的內容時,主要生命週期和及時釋放掉不需要的對象。儘量在Activity的生命週期結束時,在onDestroy中把我們做引用的其他對象做釋放,比如:cursor.close()。

第五、在很多方面使用更少的代碼去完成程序。比如:我們可以多的使用9patch圖片等。有很多細節地方都可以值得我們去發現、挖掘更多的內存問題。我們要是能做到C/C++對於程序的“誰創建,誰釋放”原則,那我們對於內存的把握,並不比Java或Android本身的GC機制差,而且更好的控制內存,能使設備運行得更流暢。 

                                                                                                                                                                        -END

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章