Android内存管理
Android是一個基于Linux實現(xiàn)的操作系統(tǒng)。但對于Linux內(nèi)核來說,Android也僅僅只是一個運行在內(nèi)核之上的應(yīng)用程序,與其他運行在內(nèi)核之上的應(yīng)用程序沒有任何區(qū)別。所以Android需要一套機制管理運行在Linux進(jìn)程中的APK應(yīng)用程序。Android內(nèi)存管理包含兩部分,一部分是Framework對內(nèi)存的管理,一部分是Linux內(nèi)核對內(nèi)存管理,這兩部分共同決定應(yīng)用程序的生命周期。本文主要闡述Android內(nèi)存管理機制的實現(xiàn)原理,以及在應(yīng)用開發(fā)中需要注意的一些事項,最后本文總結(jié)了如何實現(xiàn)殺不死進(jìn)程的一種方法。
Linux 進(jìn)程回收
在Android中,大部分應(yīng)用程序都運行在一個獨立的Linux進(jìn)程中,每個進(jìn)程都有獨立的內(nèi)存空間。隨著各種應(yīng)用程序啟動,系統(tǒng)內(nèi)存不斷下降,為了保證新應(yīng)用能夠運行,Android需要一套機制殺死暫時閑置的進(jìn)程。
Android Framework并不能直接回收內(nèi)存,其管理進(jìn)程的服務(wù)(ActivityManagerService,以下簡稱AmS)也同應(yīng)用程序一樣運行在Java虛擬機環(huán)境里。Java虛擬機都運行在各自獨立的內(nèi)存空間,所以ActivityManagerService沒有辦法感知應(yīng)用程序是否OOM。
Android系統(tǒng)中還運行了一個OOM進(jìn)程。該進(jìn)程啟動時首先會在Linux內(nèi)核中把自己注冊為一個OOM Killer。AmS需要把每一個應(yīng)用程序的oom_adj值告知OOM Killer,這個值的范圍在-16到15之間,值越低,說明越重要,這個值類似于Linux中的nice值,只在標(biāo)準(zhǔn)的Linux中,有其自己的OOM Killer。Android中的OOM Killer進(jìn)程僅僅適用于Android應(yīng)用程序。
當(dāng)內(nèi)核的內(nèi)存管理模塊檢測到系統(tǒng)內(nèi)存不足時就會通知OOM Killer,然后OOM Killer根據(jù)AmS所告知的優(yōu)先級強制退出優(yōu)先級低的應(yīng)用程序。
應(yīng)用程序在內(nèi)存中的狀態(tài)
Android官方聲稱,Activity退出后,其所在進(jìn)程并不會被立即殺死,從而在下次啟動Activity時,能夠提高啟動速度。這些Activity只有在內(nèi)存緊張時才會被系統(tǒng)殺死。所以對于應(yīng)用程序來說,關(guān)閉并不意味著釋放內(nèi)存。
Activity在內(nèi)存中的狀態(tài)?
系統(tǒng)只有一個Activity處于與用戶交互的狀態(tài),對于非交互狀態(tài)的Activity,AmS會在內(nèi)部暫時緩存起來而不是立即殺死,但如果后臺Activity數(shù)目超過一定閾值,AmS則會強制殺死一些優(yōu)先級低的Activity。以下是Activity在內(nèi)存或者說在AmS中的狀態(tài):
AmS會記錄最近啟動的20個Activity,如果超過20則舍棄最早記錄的Activity。
AmS會將所有正在運行的Activity保存在一個列表中,對于使用back返回的Activity則從列表中清除。
AmS使用Lru算法保存所有最近使用過的Activity。
AmS使用一個列表(mStoppingActivities)保存需要停止的Activity,這種情況
發(fā)生在啟動一個Activity時,AmS遵循先啟動后停止的策略,將需要停止的Activity保存在此列表中,等AmS閑置下來后再停止Activity。
AmS使用一個列表保存處于finish狀態(tài)(onDestory())的Activity,當(dāng)一個Activity處于finish狀態(tài)時(onDestory()執(zhí)行后)不會被立即殺死,而是保存到該列表中直到超過系統(tǒng)設(shè)定的警戒線才會回收該列表中的Activity。
應(yīng)用進(jìn)程在內(nèi)存中的狀態(tài)?
每個應(yīng)用程序都對應(yīng)著一個ActivityThread類,該類初始化后就進(jìn)入Looper.loop()函數(shù)中無限循環(huán)。
以后則依靠消息機制運行,既當(dāng)有消息時處理消息,沒有消息則應(yīng)用進(jìn)程進(jìn)入sleep狀態(tài)。loop()方法內(nèi)部代碼如下所示:
在Linux內(nèi)核調(diào)度中,如果一個線程的狀態(tài)為sleep,則除了占用調(diào)度本身的時間,不會占用CPU時間片。
有三種情況會喚醒應(yīng)用線程,一種是定時器中斷(比如我們設(shè)置的鬧鐘,在程序中可以設(shè)置定時任務(wù)),第二種是用戶按鍵消息,第三種是Binder消息(Binder用于進(jìn)程間通信,其在應(yīng)用程序中會自動創(chuàng)建一個線程,Binder在接收到消息后會想UI主線程發(fā)送一個消息從而使queue.next()繼續(xù)執(zhí)行)這就是所謂的消息驅(qū)動模式。
所以設(shè)計良好的應(yīng)用程序當(dāng)處于后臺時不會占用任何CPU時間,更不會拖慢系統(tǒng)運行速度。其所占用的僅僅是內(nèi)存,即使釋放所占用的內(nèi)存也不會提高系統(tǒng)運行速度。當(dāng)然這里說的是設(shè)計良好的應(yīng)用程序,目前國內(nèi)很多應(yīng)用在處于后臺狀態(tài)時依然會偷偷干很多事情,這無疑就拖慢了系統(tǒng)運行速度。
Android 內(nèi)存回收
Activity所占內(nèi)存在一般情況下不會被回收,只有在系統(tǒng)內(nèi)存不夠用時才會回收,并且回收會遵循一定規(guī)則。大致可以概括為前臺Activity最后回收,其次是包含前臺的Service或者Provider,再其次是后臺Activity,最后是空進(jìn)程。
內(nèi)存釋放的三個地方
第一個是在ActivityManagerService中運行,即Android所聲稱的當(dāng)系統(tǒng)內(nèi)存低時,優(yōu)先釋放沒有任何Activity的進(jìn)程,然后釋放非前臺Activity對應(yīng)的進(jìn)程。
第二個是在OOM Killer中,此時AmS只要告訴OOM各個應(yīng)用的優(yōu)先級,然后OOM就會調(diào)用Linux內(nèi)部的進(jìn)程管理方法殺死優(yōu)先級較低的進(jìn)程。
第三個是在應(yīng)用進(jìn)程本身之中,當(dāng)AmS認(rèn)為目標(biāo)進(jìn)程需要被殺死時,首先會通知目標(biāo)進(jìn)程進(jìn)程內(nèi)存釋放。這包括調(diào)用目標(biāo)進(jìn)程的scheduleLowMemory()方法和processInBackground()方法。
關(guān)閉Activity的三種情況
第一種,從調(diào)用startActivity()開始,一般情況下,當(dāng)前都有正在運行的Activity,所以需要先暫停當(dāng)前的Activity,而暫停完畢后,AmS會收到一個Binder消息,并開始從completePaused()處執(zhí)行。在該函數(shù)中,由于上一個Activity并沒有finishing,僅僅是stop,所以這里會把上一個Activity添加到mStoppingActivity列表中。當(dāng)目標(biāo)Activity啟動后,會向Ams發(fā)送一個請求進(jìn)行內(nèi)存回收的消息,這會導(dǎo)致AmS在內(nèi)部調(diào)用activityIdleInternal()方法,該方法中首先會處理mStoppingActivities列表中的Activity,這就會調(diào)用stopActivityLocked()方法。這又會通過IPC調(diào)用,通知應(yīng)用進(jìn)程stop指定的Activity,當(dāng)stop完畢后,再報告給AmS,于是AmS再從activityStopped()出開始執(zhí)行,而這會調(diào)用trimApplication()方法,該方法會執(zhí)行內(nèi)存相關(guān)的操作。
第二種,當(dāng)按Back鍵后,會調(diào)用finishActivityLocked(),然后把該Activity的finishing標(biāo)識設(shè)為true,然后再調(diào)用startPausingLocked(),當(dāng)目標(biāo)Activity完成暫停后,就會報告AmS,此時AmS又會從completePaused()處開始執(zhí)行。與第一種情況不同,由于此時暫停的Activity的finishing狀態(tài)已經(jīng)設(shè)置為true,所以會執(zhí)行finishingActivityLocked(),而不是像第一種情況中僅僅把該Activity添加到mStoppingActivities列表。
第三種,當(dāng)Activity啟動后,會向AmS發(fā)送一個Idle消息,這會導(dǎo)致AmS開始執(zhí)行activityIdleInternal()方法。該方法會首先處理mStoppingActivities列表中的對象,接著處理mFinishingActivities列表,最后再調(diào)用trimApplication()方法。
以上就是關(guān)閉Activity的三種情況,包括stop和destory,客戶進(jìn)程中與之對應(yīng)的就是onStop()和onDestory()的調(diào)用。
如果使用OOM還有AmS機制殺死后臺進(jìn)程后,此時運行的Activity數(shù)量依然超過MAX_ACTIVITIES(20),則需要繼續(xù)銷毀滿足以下三個條件的Activity:
Activity必須已經(jīng)stop,但卻沒有finishing
必須是不可見的,既該Activity窗口上面有其他全屏的窗口,如果不是全屏,則后面的Activity是可見的。
不能是persistent類型,既常駐進(jìn)程不能被殺死。
進(jìn)程優(yōu)先級
Android系統(tǒng)試圖盡可能長時間地保持應(yīng)用程序進(jìn)程,但為了新建或者運行更加重要的進(jìn)程,總是需要清除過時進(jìn)程來回收內(nèi)存。為了決定保留或終止哪個進(jìn)程,根據(jù)進(jìn)程內(nèi)運行的組件及這些組件的狀態(tài),系統(tǒng)把每個進(jìn)程都劃入一個“重要性層次結(jié)構(gòu)”中。重要性最低的進(jìn)程首先會被清除,然后是下一個最低的,依此類推。
重要性層次結(jié)構(gòu)共有5級,以下列表按照重要程度列出了各類進(jìn)程(第一類進(jìn)程是最重要的,將最后一個被終止):
1)前臺進(jìn)程
用戶當(dāng)前操作所必須的進(jìn)程。滿足以下任一條件時,進(jìn)程被視作處于前臺:
其中運行著正與用戶交互的Activity(Activity對象的onResume()方法已被調(diào)用)。
其中運行著與用戶交互的activity綁定的Service。
其中運行著前臺Service,既該Service以startForeground()方式被調(diào)用。
其中運行著正在執(zhí)行生命周期回調(diào)方法(onCreate()、onStart()或onDestory())的Service。
其中運行著正在執(zhí)行onReceive()方法的BroadcastReceiver。
一般而言,任何時刻前臺進(jìn)程的數(shù)量都為數(shù)不多,只有當(dāng)內(nèi)存不足以維持它們同時運行時才會被終止。通常,設(shè)備這時候已經(jīng)到了使用虛擬內(nèi)存的地步,終止一些前臺進(jìn)程是為了保證用戶界面的及時響應(yīng)。
2) 可見進(jìn)程
沒有前臺組件、但仍會影響用戶在屏幕上所見內(nèi)容的進(jìn)程。滿足以下任一條件時,進(jìn)程被認(rèn)為是可見的:
其中運行著非前臺Activity,但用戶仍然可見到此activity(onPause()方法被調(diào)用)。例如,打開了一個對話框,而activity還允許顯示在對話框后面,對用戶依然可見。
其中運行著被可見(或前臺)activity綁定的Service。
可見進(jìn)程被認(rèn)為是非常重要的進(jìn)程,除非無法維持所有前臺進(jìn)程同時運行了,否則它們是不會被終止的。
3) 服務(wù)進(jìn)程
此進(jìn)程運行著由startService()方法啟動的服務(wù),它不會升級為前臺進(jìn)程或可見進(jìn)程。盡管服務(wù)進(jìn)程不直接和用戶所見內(nèi)容關(guān)聯(lián),但他們通常在執(zhí)行一些用戶關(guān)心的操作(比如在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))。因此,除非內(nèi)存不足以維持所有前臺、可見進(jìn)程同時運行,系統(tǒng)會保持服務(wù)進(jìn)程的運行。
4) 后臺進(jìn)程
包含用戶不可見activity(Activity對象的onStop()方法已被調(diào)用)的進(jìn)程。這些進(jìn)程對用戶體驗沒有直接的影響,系統(tǒng)可能在任意時間終止它們,以回收內(nèi)存供前臺進(jìn)程、可見進(jìn)程及服務(wù)進(jìn)程使用。
通常系統(tǒng)會有很多后臺進(jìn)程在運行,所以它們被保存在一個LRU(最近最少使用)列表中,以確保最近被用戶使用的activity最后一個被終止。如果一個activity正確實現(xiàn)了生命周期方法,并保存了當(dāng)前的狀態(tài),則終止此類進(jìn)程不會對用戶體驗產(chǎn)生可見的影響。因為在用戶返回時,activity會恢復(fù)所有可見的狀態(tài)。關(guān)于保存和恢復(fù)狀態(tài)的詳細(xì)信息,請參閱Activity文檔。
5) 空進(jìn)程
不含任何活動應(yīng)用程序組件的進(jìn)程。保留這種進(jìn)程的唯一目的就是用作緩存,以改善下次在此進(jìn)程中運行組件的啟動時間。為了在進(jìn)程緩存和內(nèi)核緩存間平衡系統(tǒng)整體資源,系統(tǒng)經(jīng)常會終止這種進(jìn)程。
依據(jù)進(jìn)程中目前活躍組件的重要程度,Android會給進(jìn)程評估一個盡可能高的級別。例如,如果一個進(jìn)程中運行著一個服務(wù)和一個用戶可見的activity,則此進(jìn)程會被評定為可見進(jìn)程,而不是服務(wù)進(jìn)程。
此外,一個進(jìn)程的級別可能會由于其它進(jìn)程的依賴而被提高——為其它進(jìn)程提供服務(wù)的進(jìn)程級別永遠(yuǎn)不會低于使用此服務(wù)的進(jìn)程。比如:如果A進(jìn)程中的content provider為進(jìn)程B中的客戶端提供服務(wù),或進(jìn)程A中的服務(wù)被進(jìn)程B中的組件所調(diào)用,則A進(jìn)程至少被視為與進(jìn)程B同樣重要。
因為運行服務(wù)的進(jìn)程級別是高于后臺activity進(jìn)程的,所以,如果activity需要啟動一個長時間運行的操作,則為其啟動一個服務(wù)會比簡單地創(chuàng)建一個工作線程更好些——尤其是該操作時間比activity的生存期還要長的情況下。比如,一個activity要把圖片上傳至Web網(wǎng)站,就應(yīng)該創(chuàng)建一個服務(wù)來執(zhí)行之,即使用戶離開了此activity,上傳還是會在后臺繼續(xù)運行。不論activity發(fā)生什么情況,使用服務(wù)可以保證操作至少擁有“服務(wù)進(jìn)程”的優(yōu)先級。同理,廣播接收器broadcast receiver也是使用服務(wù)來處理耗時任務(wù)的,而不是簡單地把它放入線程中。
殺不死的Service
如何讓應(yīng)用在手機中存活更長時間?網(wǎng)上各種方法可謂是千奇百怪,有些簡直異想天開。
系統(tǒng)廣播喚醒應(yīng)用,比如手機開機,網(wǎng)絡(luò)切換等
接入第三方SDK喚醒應(yīng)用,比如接入微信SDK會喚醒微信
免殺白名單,比如360免殺白名單,MIUI系統(tǒng)免殺白名單
全家桶,應(yīng)用之間互相喚醒,比如百度系,阿里系應(yīng)用
兩個Service互相喚醒(這個就別想了,不靠譜)
使用Timer定時器(一樣不靠譜)
設(shè)計良好的應(yīng)用不應(yīng)該在用戶不使用的時候依然保持運行。一直在后臺運行不光費電費流量,還是造成系統(tǒng)卡頓的主要原因之一(參見上文分析)。正常的做法是優(yōu)化你的應(yīng)用程序,減少不合理場景的情況,除一些必要服務(wù)應(yīng)用外,大部分應(yīng)用不需要一直在后臺保存運行狀態(tài)。
有正常的做法就有不正常的做法,讓應(yīng)用長時間停留在用戶手機中無外乎就是增加所謂的活躍用戶數(shù)等一些產(chǎn)品指標(biāo)。這對于很多公司還是很有吸引力的。
如上文所說,無論應(yīng)用怎么掙扎,當(dāng)處于不可見進(jìn)程的情況下隨時都有可能被殺死。所以使用前臺進(jìn)程是最有效的方法。但前臺進(jìn)程必須有一個Notifcation顯示在通知欄中,有沒有辦法讓應(yīng)用以前臺進(jìn)程的方式啟動同時又不顯示Notifcation?方法當(dāng)然有,就是利用系統(tǒng)漏洞:
API<18,啟動前臺Service時直接傳入new Notifcation();
API>=18,同時啟動兩個id相同的前臺Service,然后再將后啟動的Service做stop處理
目前,QQ,微信,支付寶等知名應(yīng)用都使用此方案。不過如果應(yīng)用占用太多內(nèi)存即使是前臺進(jìn)程也依然會被干掉。
這些所謂的實現(xiàn)進(jìn)程殺不死的方案并不都是一勞永逸的方法,以犧牲用戶體驗為代價很有可能會激怒用戶卸載你的應(yīng)用,所以最好的方式還是遵循Android規(guī)范開發(fā)性能更優(yōu)更合理的應(yīng)用程序。
本文摘自異步社區(qū),作者:?xiangzhihong?作品:《Android內(nèi)存管理》,未經(jīng)授權(quán),禁止轉(zhuǎn)載。
推薦閱讀
2018年5月新書書單(文末福利)
2018年4月新書書單
異步圖書最全Python書單
一份程序員必備的算法書單
第一本Python神經(jīng)網(wǎng)絡(luò)編程圖書
長按二維碼,可以關(guān)注我們喲
每天與你分享IT好文。
在“異步圖書”后臺回復(fù)“關(guān)注”,即可免費獲得2000門在線視頻課程
點擊閱讀原文,查看更多內(nèi)容
閱讀原文
轉(zhuǎn)載于:https://blog.51cto.com/13127751/2128792
總結(jié)
以上是生活随笔為你收集整理的Android内存管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 先入为主与刚刚好(自省)
- 下一篇: android崩溃日志收集