防止 Android 記憶體洩漏的 8 種方法

發表於2016-09-27

在上一篇 Android記憶體洩漏的八種可能(上)中,我們討論了八種容易發生記憶體洩漏的程式碼。其中,尤其嚴重的是洩漏Activity物件,因為它佔用了大量系統記憶體。不管記憶體洩漏的程式碼表現形式如何,其核心問題在於:

在Activity生命週期之外仍持有其引用。

幸運的是,一旦洩漏發生且被定位到了,修復方法是相當簡單的。

Static Actitivities

這種洩漏

構造靜態變數持有Activity物件很容易造成記憶體洩漏,因為靜態變數是全域性存在的,所以當MainActivity生命週期結束時,引用仍被持有。這種寫法開發者是有理由來使用的,所以我們需要正確的釋放引用讓垃圾回收機制在它被銷燬的同時將其回收。

Android提供了特殊的Set類 https://developer.android.com/reference/java/lang/ref/package-summary.html#classes 允許開發者控制引用的“強度”。Activity物件洩漏是由於需要被銷燬時,仍然被強引用著,只要強引用存在就無法被回收。

可以用弱引用代替強引用。
https://developer.android.com/reference/java/lang/ref/WeakReference.html.

弱引用不會阻止物件的記憶體釋放,所以即使有弱引用的存在,該物件也可以被回收。

Static Views

靜態變數持有View

private static View view;

由於View持有其宿主Activity的引用,導致的問題與Activity一樣嚴重。弱引用是個有效的解決方法,然而還有另一種方法是在生命週期結束時清除引用,Activity#onDestory()方法就很適合把引用置空。

Inner Class

這種洩漏

於上述兩種情況相似,開發者必須注意用非靜態內部類,因為靜態內部類持有外部類的隱式引用,容易導致意料之外的洩漏。然而內部類可以訪問外部類的私有變數,只要我們注意引用的生命週期,就可以避免意外的發生。

避免靜態變數

這樣持有內部類的成員變數是可以的。

Anonymous Classes

前面我們看到的都是持有全域性生命週期的靜態成員變數引起的,直接或間接通過鏈式引用Activity導致的洩漏。這次我們用AsyncTask

Handler

Thread

全部都是因為匿名類導致的。匿名類是特殊的內部類——寫法更為簡潔。當需要一次性的特殊子類時,Java提供的語法糖能讓表示式最少化。這種很贊很偷懶的寫法容易導致洩漏。正如使用內部類一樣,只要不跨越生命週期,內部類是完全沒問題的。但是,這些類是用於產生後臺執行緒的,這些Java執行緒是全域性的,而且持有建立者的引用(即匿名類的引用),而匿名類又持有外部類的引用。執行緒是可能長時間執行的,所以一直持有Activity的引用導致當銷燬時無法回收。
這次我們不能通過移除靜態成員變數解決,因為執行緒是於應用生命週期相關的。為了避免洩漏,我們必須捨棄簡潔偷懶的寫法,把子類宣告為靜態內部類。

靜態內部類不持有外部類的引用,打破了鏈式引用。

所以對於AsyncTask

Handler

TimerTask

但是,如果你堅持使用匿名類,只要在生命週期結束時中斷執行緒就可以。

Sensor Manager

這種洩漏

使用Android系統服務不當容易導致洩漏,為了Activity與服務互動,我們把Activity作為監聽器,引用鏈在傳遞事件和回撥中形成了。只要Activity維持註冊監聽狀態,引用就會一直持有,記憶體就不會被釋放。

在Activity結束時登出監聽器

總結

Activity洩漏的案例我們已經都走過一遍了,其他都大同小異。建議日後遇到類似的情況時,就使用相應的解決方法。記憶體洩漏只要發生過一次,通過詳細的檢查,很容易解決並防範於未然。

是時候做最佳實踐者了!

相關文章