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