Android解決記憶體洩漏的實戰記錄
前言
關於Android記憶體優化、記憶體洩漏相關的文章很多,不過大部分是給讀者知識上的準備。本文將記錄和分享本人在專案中的實戰經驗,希望能從另一個角度寫出價值~~
知識儲備
首先,理解一下什麼是記憶體洩漏:當應用不再需要這個物件,但仍未釋放該物件的所有引用。這樣解釋有點抽象。或者這樣說,一般情況下,在我們Android開發中,當一個Activity物件自身執行onDestroy()後,仍被其他物件持有者,使JVM不能回收該Activity物件,就造成了記憶體洩漏(這裡讀者必須要對垃圾回收的可達性分析法有一定理解,這裡不作詳解,可參考Carson的一篇文章:JVM:判斷一個Java物件是否存活 )。
實戰紀錄
通過Leak Canary的監測和翻查GitLab上的Commits記錄,大致把記憶體洩漏的成因分為以下幾種情況。
情況一:屬性動畫忘記取消
這種錯誤在實戰中出現得比較多。特別是那種設定了無限迴圈模式的動畫,理論上是一直不會釋放的,積累多幾個介面的洩漏就很容易造成OOM。如果不是無限迴圈模式,理論上等動畫播完後就會在適當時候被回收,危害性還不算大。
為什麼動畫會導致記憶體洩漏呢?我猜是動畫(數值)的更新肯定也離不開執行緒間的通訊機制,而handler肯定得持有介面控制元件的引用才能更新動畫,控制元件又持有Activity例項,所以就回收不到Activity了。檢視動畫的start()方法,裡面有個AnimationHandler,嗯,八九不離十了。
解決方法:記得在onDestroy()方法中取消掉動畫就行,特別是無限迴圈的動畫!!
情況二:RxJava的定時任務沒有取消
當在頁面中通過Observable.interval()執行迴圈任務或者delay()延時任務等,退出介面忘記執行Subscription.unsubscribe()來取消任務。
洩漏的原因和危害性不用多說,可參考情況一。
解決方法:記得在onDestroy()方法中取消掉RxJava任務就行,特別是無限迴圈的任務!!
情況三:第三方服務的耗時操作
在使用到七牛上傳檔案的頁面裡,當上傳任務還未執行完,使用者就按返回鍵退回上一介面,而未為使用者取消上傳任務。
因為上傳任務的回撥是直接引用著該Activity物件的,所以上傳未完成的話,Activity物件也回收不了。
解決方法:在onDestroy()方法中通知第三方服務取消任務就行。一般第三方服務都會考慮到這個問題,可以很方便的取消任務。
情況四:EventBus沒有反註冊
忘記在onDestroy()方法中執行EventBus.getDefault().unregister()方法。
顯然,EventBus會持有此Activity物件,無法回收。
解決方法:在onDestroy()方法中執行EventBus.getDefault().unregister()。
關於handler的處理方法
對於網上經常說的非靜態Handler導致的記憶體洩漏,我基本沒測到過,因為我一直有在onDestroy()方法中執行handler.removeCallbacksAndMessages(null);來清空handler對應的訊息佇列。而網上更多文章推薦的靜態+弱引用,我覺得從邏輯上來看不夠針對性,程式碼編寫起來也十分繁雜。因為我基本都是用removeCallbacksAndMessages()方法。具體的測試和對比可以參考這篇文章:Handler 造成 Activity 洩漏,用弱引用真的有用麼?
總結
Android的記憶體洩漏的具體場景可以有千種萬種,但核心只有一個,就是Activity執行onDestroy()後還被其他物件持有著,導致JVM通過可達性分析法作出不回收該Activity物件的判斷,導致記憶體洩漏(當然如果你要說你的Activity在onDestroy()後還有特定的存在價值,算不上洩漏;或者你的洩漏物件不是Activity而是Service這些罕見情況,我也是無力反駁的對吧)。
解決Android記憶體洩漏的核心也只有這個,在onDestroy()確保這個Activity物件不再被其他物件引用著。這樣就確保不會發生記憶體洩漏了,最起碼按Leak Canary對記憶體洩漏的定義是這樣的。
當然以上也只是理論上的理想情況實際上國產的安卓機還有各種坑。我手上的華為Mate,EMUI版本8.0.0,Android版本8.0,就經常檢測到一個什麼mLastClickView的記憶體洩漏,我Google了一下,應該是華為系統的bug,其他國產定製手機也遇到過類似的奇奇怪怪的記憶體洩漏。對於這種系統級別的bug導致的記憶體洩漏,我們也無能為力的~我們作為應用開發者,在應用層上在自己的專案中確保不產生記憶體洩漏就行。