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