Android中常見的內存泄漏

MarceloMccl 9年前發布 | 6K 次閱讀 安卓開發 Android開發 移動開發

寫在前面

雖然現在手機的內存不斷增大,但Android為了實現不同應用間運行隔離,不至于相互影響,所以對單個應用最大可使用的內存做出了限制。限制大小在不同手機設備和ROM上都可能不一樣。如Android界的第一款手機HTC G1是16MB,后來的Nexus One是32MB。所以即使手機內存不斷變大,但你開發的應用可使用的內存空間并沒有增大很多,這也需要你開發時多注意注意內存問題,遵從最少使用內存的原則,避免內存泄漏的發生,這樣不但能讓你的應用避免被系統無故殺死,還能讓用戶使用更加流暢。

如果想查看自己應用可以使用的最大內存空間,可以參考: 《Detect application heap size in Android》

如果你實在需要增大自己應用的內存使用大小,可以參考這篇文章: 《How to increase heap size of an android application》

內存泄漏的產生

Android的虛擬機機制模仿JVM,所以也有垃圾回收機制。Android虛擬機中把內存分為兩部分,一部分為棧空間,存儲一些全局引用和靜態變量等值,該空間的分配與回收由系統機制決定,垃圾回收不作用在這塊區域;另一部分為堆空間,里面存儲是對象的實例,需要開發者主動創建,垃圾回收主要作用在這部分,回收的一個主要策略是檢測堆中的對象在棧空間有無對應的引用。如果沒有引用指向它,則會被優先回收,如果有引用指向則不會被回收。所以如果開發者沒有在適當的時間把一個對象的引用設置為null,則就會可能會產生內存泄漏。在Android中最常見的一個內存泄漏問題就是長時間持有Context。Context在Android中有非常大的作用,比如用來獲取資源,所以基本上所有的視圖都需要獲得Context才能被創建。使用不當則很可能造成內存泄漏。

Android中內存泄漏表現

你開發了一個應用,剛開始使用起來還挺流暢,但隨著使用時間變長,應用就變得越來越慢,最后導致用戶不得重啟應用才能繼續使用。這就很可能出現了內存泄漏。就像上面提到的,如果說一個靜態變量持有了一個Activity的引用,用戶打開該Activity,會創建一個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); }</code></pre>

以上使用一個靜態變量來保存一個drawable。從上分析可以看到,一個TextView的局部變量持有了本Activity的引用,因為label是局部變量,所以并不會引起內存泄漏。但緊接著下面,使用了 label.setBackgroundDrawable(sBackground); 有人可能就會想,這也沒啥問題啊,即使 sBackground 作為一個靜態變量,持有了一個drawable,這塊內存不會被釋放,但這塊內存畢竟沒有持有整個Activity的引用。但實際上你錯了。我們來看下View.java中的setBackgroundDrawable源碼,源碼位置在

(frameworks/base/core/java/android/view/View.java)

public void setBackgroundDrawable(Drawable background) {
        ...

    if (background != null) {
        ...

        background.setCallback(this);
        ...
    } else {
        ...
    }

    ...

}</code></pre>

其中有一個 background.setCallback(this); ,所以這就導致這個靜態變量指向的對象又持有了TextView這個對象的引用。這樣,因為是靜態變量,像我上一小節所說的,靜態變量的生命周期基本和應用同周期,它持有了TextView對象引用,所以TextView不會被回收,然后TextView又持有了整個Activity的引用,所以最后就導致整個Activity在關閉后也不會被系統回收。

當然解決此種問題的方法非常簡單,就是把 sBackground 換成非靜態變量就行,這樣當Activity關閉后,回收機制就能判斷,這個Activity的空間不會被使用到了,所以就啟動GC。

另一個例子

下面我們再舉一個非常常見的例子,Android開發者很喜歡用單例模式,但有些開發者不注意就可能導致內存泄漏,如下:

private static DaVinci sDaVinci = null;

public static DaVinci with(Context context) { if ( sDaVinci == null ) { sDaVinci = new DaVinci(context); } return sDaVinci; }</code></pre>

大家可能一時覺得這沒啥問題啊,但這并不是一個好的寫法,因為這可能讓用戶在使用時把一個Activity的Context傳入,導致讓一個單例持有了這個Activity的Context引用,造成內存泄漏。一個比較好的寫法是使用

sDaVinci = new DaVinci(context.getApplicationContext()); 。因為Application的生命周期本來就是貫穿整個應用的,所以即使被持有也沒關系。

幾點建議

1,盡量不要用一個生命周期長于Activity的對象來持有Activity的引用。

2,在需要傳入Context的時候盡量考慮使用Application的Context,而不是Activity的。

3,在Activity中盡量避免使用生命周期不受控制的非靜態類型的內部類,可以使用靜態類型的內部類加上弱引用的方式實現。

 

 

來自:http://www.androidchina.net/5523.html

 

 本文由用戶 MarceloMccl 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!