Android中常見的內(nèi)存泄漏

寫在前面

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

如果想查看自己應(yīng)用可以使用的最大內(nèi)存空間,可以參考:《Detect application heap size in Android》
如果你實在需要增大自己應(yīng)用的內(nèi)存使用大小,可以參考這篇文章:《How to increase heap size of an android application》

內(nèi)存泄漏的產(chǎn)生

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

Android中內(nèi)存泄漏表現(xiàn)

你開發(fā)了一個應(yīng)用,剛開始使用起來還挺流暢,但隨著使用時間變長,應(yīng)用就變得越來越慢,最后導(dǎo)致用戶不得重啟應(yīng)用才能繼續(xù)使用。這就很可能出現(xiàn)了內(nèi)存泄漏。就像上面提到的,如果說一個靜態(tài)變量持有了一個Activity的引用,用戶打開該Activity,會創(chuàng)建一個Activity的實例,此時即使你關(guān)閉該Activity,雖然它不再顯示,但它的實例一直會在內(nèi)存中存在,因為有一個靜態(tài)變量一直指向它,導(dǎo)致它的內(nèi)存空間就不會被當(dāng)做垃圾回收。想想這個Activity中可能包含很多屬性,很多視圖的信息,它未被釋放,會浪費很多內(nèi)存空間。下面我們從兩個個例子入手,講解下內(nèi)存泄漏和解決辦法。

一個例子

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

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

public void setBackgroundDrawable(Drawable background) {
        ...

        if (background != null) {
            ...

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

        ...
}

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

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

另一個例子

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

private static DaVinci sDaVinci = null;

public static DaVinci with(Context context) {
    if ( sDaVinci == null ) {
        sDaVinci = new DaVinci(context);
    }
    return sDaVinci;
}

大家可能一時覺得這沒啥問題啊,但這并不是一個好的寫法,因為這可能讓用戶在使用時把一個Activity的Context傳入,導(dǎo)致讓一個單例持有了這個Activity的Context引用,造成內(nèi)存泄漏。一個比較好的寫法是使用
sDaVinci = new DaVinci(context.getApplicationContext());。因為Application的生命周期本來就是貫穿整個應(yīng)用的,所以即使被持有也沒關(guān)系。

幾點建議

1,盡量不要用一個生命周期長于Activity的對象來持有Activity的引用。
2,在需要傳入Context的時候盡量考慮使用Application的Context,而不是Activity的。
3,在Activity中盡量避免使用生命周期不受控制的非靜態(tài)類型的內(nèi)部類,可以使用靜態(tài)類型的內(nèi)部類加上弱引用的方式實現(xiàn)。

作者簡介
彭濤(@彭濤me) 致力于讓技術(shù)變得易懂且有趣
個人博客:http://pengtao.me, GitHub地址:https://github.com/CPPAlien

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ###集合類泄漏 集合類如果僅僅有添加元素的方法,而沒有相應(yīng)的刪除機制,導(dǎo)致內(nèi)存被占用。如果這個集合類是全局性的變...
    RunningTeemo閱讀 628評論 0 0
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,234評論 25 708
  • 轉(zhuǎn)載請注明出處:【huachao1001的簡書:http://www.itdecent.cn/users/0a7e...
    huachao1001閱讀 2,401評論 2 26
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏...
    _痞子閱讀 1,703評論 0 8
  • 有些人一輩子只能活在記憶里 一輩子相離之后再也不要相遇 然后默默的放在心里 我總以為時間過得很慢 可是三年快過去了...
    黃小花花閱讀 282評論 2 1

友情鏈接更多精彩內(nèi)容