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