記憶體洩漏對每一位 Android 開發一定是司空見慣,大家或多或少都肯定有些許接觸。大家都知道,每一個手機都有一定的承載上限,多處的記憶體洩漏堆積一定會堆積如山,最終出現記憶體爆炸 OOM。
而這,也是極有可能在 Android 面試中一道常見的開放題。
記憶體洩漏的根本原因是一個長生命週期的物件持有了一個短生命週期的物件。如果你對垃圾回收機制有所瞭解,我想這個問題基本難不住你,因為知道了原理,自然不會去觸碰這些極易導致記憶體洩漏的雷區。
該題重在積累,不需要死記硬背,自己多總結即可。
1. 長生命週期物件持有 Activity
這基本是最常見的記憶體洩漏了,比如
- 內部類形式使用 Handler 同時傳送延時訊息,或者在 Handler 裡面執行耗時任務,在任務還沒完成的時候 Activity 需要銷燬。這時候由於 Handler 持有 Activity 的強引用導致 Activity 無法被回收。
- 同理內部類形式的使用 AsyncTask 執行耗時任務也會導致記憶體洩漏的發生。
- 單例作為最長生命週期的物件,自然不應該持有 Activity 從而導致記憶體洩漏發生;
針對上面這種情況,基本不必多說了,不要使用內部類或者匿名內部類做這樣的處理就好了,實際上 IDE 也會彈出警告,我想大家應該還是都知道採用靜態內部類或者在銷燬頁面的時候使用相關方法移除處理的。
Activity
中匿名使用Handler
實際上會導致Handler
內部類持有外部類的引用,而SendMessage()
的時候Message
會持有Handler
,enqueueMessage
機制又會導致MeassageQueue
持有Message
。所以當傳送的是延遲訊息那麼Message
並不會立即的遍歷出來處理而是阻塞到對應的Message
觸發時間以後再處理。那麼阻塞的這段時間中頁面銷燬一定會造成記憶體洩漏。
2. 各種註冊操作沒有對應的反註冊
這一點基本不必多說,相信大家剛剛開始學習廣播和 Service 的時候一定對此有所接觸,然後就是比如我們常用的第三方框架 EventBus 也是一樣的。平時使用的時候注意在對應的生命週期方法中進行反註冊。
3. Bitmap 使用完沒有注意 recycle()
Bitmap 作為大物件,在使用完畢一定要注意呼叫 recycle()
進行回收。TypedArray
、Cursor
、各種流同理,一定要在最後呼叫自己的回收關閉方法處理。
4. WebView 使用不當
WebView 是非常常用的控制元件,但稍有不注意也會導致記憶體洩漏。記憶體洩漏的場景: 很多人使用 Webview 都喜歡採用佈局引用方式, 這其實也是作為記憶體洩漏的一個隱患。當 Activity 被關閉時,Webview 不會被 GC 馬上回收,而是提交給事務,進行佇列處理,這樣就造成了記憶體洩漏, 導致 Webview 無法及時回收。
目前所知的比較安全的方案是:
- 在佈局中動態新增 WebView。
- 採用下面的方法。
override fun onDestroy() {
webView?.apply {
val parent = parent
if (parent is ViewGroup) {
parent.removeView(this)
}
stopLoading()
// 退出時呼叫此方法,移除繫結的服務,否則某些特定系統會報錯
settings.javaScriptEnabled = false
clearHistory()
removeAllViews()
destroy()
}
}
5. 迴圈引用
迴圈引用導致記憶體洩漏比較少見,正常來講不會有人寫出 A 持有 B,B 持有 C,C 又持有A 這樣的程式碼,不過總還是需要注意。
總的來說,記憶體洩漏很常見,但檢測方式也很多。我們的 Android Studio 自帶的 Monitors 就可以幫我們找到大部分記憶體問題,當然我們也可以採用譬如 LeakCanary 這樣的庫去做檢測。