Android 開發,跳不過的內存管理
一、前言
在 Android 系統中,當運行的 App 被移動到后臺的之后,Android 為了保證下次啟動的速度,會將它移入 Cached 的狀態,這個時候實際上,該 App 的進程依然存在,但是對應的組件是否存在,就不一定了。而既然該 App 的進程還存活著,下次啟動的速度就會很快,這就是常說的熱啟動。
但是這些被退出到后臺的 App ,也并不是完全安全不會被清理掉的,他們可能只是沒有持有任何的組件,并且是不占用 CPU 資源的,但是它依然會占據內存空間。而當系統認為內存不足的時候,就會按照優先級清理掉一些優先級不那么高的進程,來回收一些內存空間,供新啟動的程序使用,這就是 LowMemoryKiller 的策略。
那么,為了讓我們的 App 在后臺盡可能活的久一點,無非就是將內存降低,從而使得優先級提高,而實現不被系統回收的功能(這是一個常規的優化方案,而非保活方案)。那么,如果我們能明確當前 App 處于什么狀態,就能在此時機,釋放一些不需要持有的內存資源,來達到我們的目的。
這個時候,就需要用到 onTrimMemory() 這個回調方法了。
二、什么是 onTrimMemory
1、onTrimMemory 的作用
前面提到,我們可以通過實現 onTrimMemory() 方法,來完成對當前 App 在內存中的優先級的簡單管理。
而 onTrimMemory() 回調方法,是 Android Level 14(Android 4.0) 之后提供的一個 API,它主要的作用是提醒開發者,在系統內存不足的時候,應該通過釋放部分不重要的內存資源,從而避免被 Android 系統服務殺掉。
可以看到,onTrimMemory() 本質上是一種告知 App 處于系統內存回收的不同階段的時機,應該在這些時機,合理對自身持有的內存進行釋放,以避免被系統直接殺掉,從而讓保證下次用戶啟動 App 時候的速度。
onTrimMemory() 的完整方法簽名如下:
- public void onTrimMemory(int level)
可以看到,它實際上,會有一個 level 參數來標記當前的 App 在內存中的級別,也就意味著 onTrimMemory() 方法,可能會被多次調用到。
2、 onTrimMemory 回傳的參數的意義
既然 onTrimMemory() 方法會傳遞一個 level 參數,那么就先來看看,各種 level 參數所代表的含義。
- TRIM_MEMORY_UI_HIDDEN:App 的所有 UI 界面被隱藏,最常見的就是 App 被 home 鍵或者 back 鍵,置換到后臺了。
- TRIM_MEMORY_RUNNING_MODERATE:表示 App 正常運行,并且不會被殺掉,但是目前手機內存已經有點低了,系統可能會根據 LRU List 來開始殺進程。
- TRIM_MEMORY_RUNNING_LOW:表示 App正常運行,并且不會被殺掉。但是目前手機內存已經非常低了。
- TRIM_MEMORY_RUNNING_CRITICAL:表示 App 正在正常運行,但是系統已經開始根據 LRU List 的緩存規則殺掉了一部分緩存的進程。這個時候應該盡可能的釋放掉不需要的內存資源,否者系統可能會繼續殺掉其他緩存中的進程。
- TRIM_MEMORY_BACKGROUND:表示 App 退出到后臺,并且已經處于 LRU List 比較靠后的位置,暫時前面還有一些其他的 App 進程,暫時不用擔心被殺掉。
- TRIM_MENORY_MODERATE:表示 App 退出到后臺,并且已經處于 LRU List 中間的位置,如果手機內存仍然不夠的話,還是有被殺掉的風險的。
- TRIM_MEMORY_COMPLETE:表示 App 退出到后臺,并且已經處于 LRU List 比較考靠前的位置,并且手機內存已經極低,隨時都有可能被系統殺掉。
其實從 level 值的取名來看,大致可以分為三類:
- UI 置于后臺:TRIM_MEMORY_UI_HIDDEN 。
- App 正在前臺運行時候的狀態:TRIM_MEMORY_RUNNING_Xxx
- App 被置于后臺,在 Cached 狀態下的回調:TRIM_MEMORY_Xxx
這三類中,通常我們只需要關心 App 被置于 Cached 狀態下的情況,因為系統是不會殺掉一個正在前臺運行的 App 的(但可能會觸發 OOM),但是如果該 App 有一些后臺服務正在運行,這個服務也是有被殺的風險的。
而在 Cached 狀態下的時候,當收到 TRIM_MEMORY_Xxx 的回調,就需要注意了,這些只是標記了當前 App 處于 LRU List 的位置,也就是說,如果回收了靠前的 App 進程之后,依然達不到內存使用的要求,可能會進一步去殺進程,也就是說,極端情況下,可能從 TRIM_MEMORY_BACKGROUND 到 TRIM_MEMORY_COMPLETE 是瞬間完成的事情,所以我們需要慎重處理它們,盡量對這三個狀態都進行判斷,然后做統一的回收內存資源的處理。
3、哪些組件可以監聽 onTrimMemory
既然說到了 onTrimMemory() 回掉,看樣子它是和 App 相關的,所以最少在 Application 中,應該是可以對其進行重寫來監聽回調的。但是除了 Application,其他的一些組件中,也是可以監聽它的。
這些可以監聽 onTrimMemory 的組件有:
- Application
- Activity
- Fragment
- Service
- ContentProvider
4、自定義 onTrimMemroy 監聽
除了前面提到的系統默認可以監聽 onTrimMemory() 的組件之外,我們還可以自定義 onTrimMemory 的監聽。
自定義起來也非常的簡單,只需要實現 ComponentCallbacks2 接口,然后調用 Application.registerComponentCallbacks() 方法注冊即可。
除了 registerComponentCallbacks() 方法進行注冊監聽之外,如果不使用了的話,還可以使用 unregisterComponentCallbacks() 進行解注。
那么這里是如何實現的呢?讓我們來看看 Application 的對應源碼。
可以看到,它實際上是通過一個 mComponentCallbacks 的列表進行維護的。
而在 onTrimMemory() 的時候,又從 mComponentCallbacks 中獲取到所有的 callbacks 對象,進行消息的分發。
通過這種方式實現了對 onTrimMemory() 的自定義監聽。
而 onTrimMemory() 方法同時被標記為 @CallSuper,也就嚴格要求了重寫它的子類,必須調用父類中的 onTrimMemory() 方法,從而保證了消息的分發不會缺失。
5、onLowMemory()
onTrimMemory() 既然是 Android 4.0 才新增加的 Api,那么對于低版本的設備而言,可以監聽 onLowMemory() 方法,它大概可以等同于 level 級別為 TRIM_MEMORY_COMPLETE 的回調。
當然,ComponentCallbacks2 接口繼承的 ComponentCallback 接口,也是需要實現 onLowMemory() 方法的。
三、onTrimMemory 的一些思考?
1、為什么需要 onTrimMemory()
Android 系統會在自身內存不足的情況下,清理掉一些不重要的進程來釋放內存資源,以供優先級更高的進程使用。而這個順序,主要是按照 LRU List 中的優先級來清理的,但是它也同時會考慮清理掉哪些占用內存較高的進程來讓系統更快的釋放跟多的內存。
所以,盡可能的讓 App 在系統內,占用足夠小的內存資源,就可以降低被殺的概率,從而下次啟動的時候走熱啟動的方式,提升用戶的體驗。
換一個角度來說,讓 App 占用較小的內存,也可以優化系統的速度,畢竟系統清理進程釋放內存的過程,也是需要占用 CPU 資源的。在大環境下,也是有意義的。
所以,在 onTrimMemory() 的時機,對當前 App 的內存進行釋放優化,就尤為重要了。
2、在 onTrimMemory 回調中,應該釋放哪些資源
在 onTrimMemory() 回調中,應該在一些狀態下清理掉不重要的內存資源。在不考慮內存泄露的情況下,有一些資源是我們主動緩存起來,以便我們在使用的過程中可以快速獲取,而這部分資源就是我們清理的重點。
對于這些緩存,只要是讀進內存內的都算,例如最常見的圖片緩存、文件緩存等。拿圖片緩存來說,市場上,常規的圖片加載庫,一般而言都是三級緩存,所以在內存吃緊的時候,我們就應該優先清理掉這部分圖片緩存,畢竟圖片是吃內存大戶,而且再次回來的時候,雖然內存中的資源被回收掉了,我們依然可以從磁盤或者網絡上恢復它。
除了資源緩存之外,還有一些頁面相關的資源,也是占據內存的,可以考慮清理掉 Activity Task 中,多余的 Activity,只保留 Root Activity 。
其實核心思想,就是根據 onTrimMemory() 回調的一些信息,來釋放我們持有的可被恢復,不那么重要的內存資源,以提高系統性能,已經保證當前 App 的進程不那么容易被系統回收。
【本文為51CTO專欄作者“張旸”的原創稿件,轉載請通過微信公眾號聯系作者獲取授權】