Android是一個(gè)基于Linux實(shí)現(xiàn)的操作系統(tǒng)宫补。但對(duì)于Linux內(nèi)核來(lái)說(shuō)只壳,Android也僅僅只是一個(gè)運(yùn)行在內(nèi)核之上的應(yīng)用程序饵筑,與其他運(yùn)行在內(nèi)核之上的應(yīng)用程序沒(méi)有任何區(qū)別腰湾。所以Android需要一套機(jī)制管理運(yùn)行在Linux進(jìn)程中的APK應(yīng)用程序雷恃。Android內(nèi)存管理包含兩部分,一部分是Framework對(duì)內(nèi)存的管理费坊,一部分是Linux內(nèi)核對(duì)內(nèi)存管理倒槐,這兩部分共同決定應(yīng)用程序的生命周期。本文主要闡述Android內(nèi)存管理機(jī)制的實(shí)現(xiàn)原理附井,以及在應(yīng)用開(kāi)發(fā)中需要注意的一些事項(xiàng)讨越,最后本文總結(jié)了如何實(shí)現(xiàn)殺不死進(jìn)程的一種方法。
Linux 進(jìn)程回收
在Android中永毅,大部分應(yīng)用程序都運(yùn)行在一個(gè)獨(dú)立的Linux進(jìn)程中把跨,每個(gè)進(jìn)程都有獨(dú)立的內(nèi)存空間。隨著各種應(yīng)用程序啟動(dòng)沼死,系統(tǒng)內(nèi)存不斷下降着逐,為了保證新應(yīng)用能夠運(yùn)行,Android需要一套機(jī)制殺死暫時(shí)閑置的進(jìn)程漫雕。
Android Framework并不能直接回收內(nèi)存滨嘱,其管理進(jìn)程的服務(wù)(ActivityManagerService,以下簡(jiǎn)稱(chēng)AmS)也同應(yīng)用程序一樣運(yùn)行在Java虛擬機(jī)環(huán)境里浸间。Java虛擬機(jī)都運(yùn)行在各自獨(dú)立的內(nèi)存空間太雨,所以ActivityManagerService沒(méi)有辦法感知應(yīng)用程序是否OOM。
Android系統(tǒng)中還運(yùn)行了一個(gè)OOM進(jìn)程魁蒜。該進(jìn)程啟動(dòng)時(shí)首先會(huì)在Linux內(nèi)核中把自己注冊(cè)為一個(gè)OOM Killer囊扳。AmS需要把每一個(gè)應(yīng)用程序的oom_adj值告知OOM Killer吩翻,這個(gè)值的范圍在-16到15之間,值越低锥咸,說(shuō)明越重要狭瞎,這個(gè)值類(lèi)似于Linux中的nice值,只在標(biāo)準(zhǔn)的Linux中搏予,有其自己的OOM Killer熊锭。Android中的OOM Killer進(jìn)程僅僅適用于Android應(yīng)用程序。
當(dāng)內(nèi)核的內(nèi)存管理模塊檢測(cè)到系統(tǒng)內(nèi)存不足時(shí)就會(huì)通知OOM Killer雪侥,然后OOM Killer根據(jù)AmS所告知的優(yōu)先級(jí)強(qiáng)制退出優(yōu)先級(jí)低的應(yīng)用程序碗殷。
應(yīng)用程序在內(nèi)存中的狀態(tài)
Android官方聲稱(chēng),Activity退出后速缨,其所在進(jìn)程并不會(huì)被立即殺死锌妻,從而在下次啟動(dòng)Activity時(shí),能夠提高啟動(dòng)速度旬牲。這些Activity只有在內(nèi)存緊張時(shí)才會(huì)被系統(tǒng)殺死仿粹。所以對(duì)于應(yīng)用程序來(lái)說(shuō),關(guān)閉并不意味著釋放內(nèi)存原茅。
Activity在內(nèi)存中的狀態(tài)
系統(tǒng)只有一個(gè)Activity處于與用戶(hù)交互的狀態(tài)吭历,對(duì)于非交互狀態(tài)的Activity,AmS會(huì)在內(nèi)部暫時(shí)緩存起來(lái)而不是立即殺死员咽,但如果后臺(tái)Activity數(shù)目超過(guò)一定閾值毒涧,AmS則會(huì)強(qiáng)制殺死一些優(yōu)先級(jí)低的Activity。以下是Activity在內(nèi)存或者說(shuō)在AmS中的狀態(tài):
- AmS會(huì)記錄最近啟動(dòng)的20個(gè)Activity贝室,如果超過(guò)20則舍棄最早記錄的Activity契讲。
- AmS會(huì)將所有正在運(yùn)行的Activity保存在一個(gè)列表中,對(duì)于使用back返回的Activity則從列表中清除滑频。
- AmS使用Lru算法保存所有最近使用過(guò)的Activity捡偏。
- AmS使用一個(gè)列表(mStoppingActivities)保存需要停止的Activity,這種情況
發(fā)生在啟動(dòng)一個(gè)Activity時(shí)峡迷,AmS遵循先啟動(dòng)后停止的策略银伟,將需要停止的Activity保存在此列表中,等AmS閑置下來(lái)后再停止Activity绘搞。 - AmS使用一個(gè)列表保存處于finish狀態(tài)(onDestory())的Activity彤避,當(dāng)一個(gè)Activity處于finish狀態(tài)時(shí)(onDestory()執(zhí)行后)不會(huì)被立即殺死,而是保存到該列表中直到超過(guò)系統(tǒng)設(shè)定的警戒線才會(huì)回收該列表中的Activity夯辖。
應(yīng)用進(jìn)程在內(nèi)存中的狀態(tài)
每個(gè)應(yīng)用程序都對(duì)應(yīng)著一個(gè)ActivityThread類(lèi)琉预,該類(lèi)初始化后就進(jìn)入Looper.loop()函數(shù)中無(wú)限循環(huán)。
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
Looper.loop();
以后則依靠消息機(jī)制運(yùn)行蒿褂,既當(dāng)有消息時(shí)處理消息圆米,沒(méi)有消息則應(yīng)用進(jìn)程進(jìn)入sleep狀態(tài)卒暂。loop()方法內(nèi)部代碼如下所示:
public static final loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while(true){
Message msg = queue.next();// might block
...
}
}
在Linux內(nèi)核調(diào)度中,如果一個(gè)線程的狀態(tài)為sleep娄帖,則除了占用調(diào)度本身的時(shí)間也祠,不會(huì)占用CPU時(shí)間片。
有三種情況會(huì)喚醒應(yīng)用線程近速,一種是定時(shí)器中斷(比如我們?cè)O(shè)置的鬧鐘诈嘿,在程序中可以設(shè)置定時(shí)任務(wù)),第二種是用戶(hù)按鍵消息削葱,第三種是Binder消息(Binder用于進(jìn)程間通信永淌,其在應(yīng)用程序中會(huì)自動(dòng)創(chuàng)建一個(gè)線程,Binder在接收到消息后會(huì)想U(xiǎn)I主線程發(fā)送一個(gè)消息從而使queue.next()繼續(xù)執(zhí)行)這就是所謂的消息驅(qū)動(dòng)模式佩耳。
所以設(shè)計(jì)良好的應(yīng)用程序當(dāng)處于后臺(tái)時(shí)不會(huì)占用任何CPU時(shí)間,更不會(huì)拖慢系統(tǒng)運(yùn)行速度谭跨。其所占用的僅僅是內(nèi)存干厚,即使釋放所占用的內(nèi)存也不會(huì)提高系統(tǒng)運(yùn)行速度。當(dāng)然這里說(shuō)的是設(shè)計(jì)良好的應(yīng)用程序螃宙,目前國(guó)內(nèi)很多應(yīng)用在處于后臺(tái)狀態(tài)時(shí)依然會(huì)偷偷干很多事情蛮瞄,這無(wú)疑就拖慢了系統(tǒng)運(yùn)行速度。
Android 內(nèi)存回收
Activity所占內(nèi)存在一般情況下不會(huì)被回收谆扎,只有在系統(tǒng)內(nèi)存不夠用時(shí)才會(huì)回收挂捅,并且回收會(huì)遵循一定規(guī)則。大致可以概括為前臺(tái)Activity最后回收堂湖,其次是包含前臺(tái)的Service或者Provider闲先,再其次是后臺(tái)Activity,最后是空進(jìn)程无蜂。
內(nèi)存釋放的三個(gè)地方
- 第一個(gè)是在ActivityManagerService中運(yùn)行伺糠,即Android所聲稱(chēng)的當(dāng)系統(tǒng)內(nèi)存低時(shí),優(yōu)先釋放沒(méi)有任何Activity的進(jìn)程斥季,然后釋放非前臺(tái)Activity對(duì)應(yīng)的進(jìn)程训桶。
- 第二個(gè)是在OOM Killer中,此時(shí)AmS只要告訴OOM各個(gè)應(yīng)用的優(yōu)先級(jí)酣倾,然后OOM就會(huì)調(diào)用Linux內(nèi)部的進(jìn)程管理方法殺死優(yōu)先級(jí)較低的進(jìn)程舵揭。
- 第三個(gè)是在應(yīng)用進(jìn)程本身之中,當(dāng)AmS認(rèn)為目標(biāo)進(jìn)程需要被殺死時(shí)躁锡,首先會(huì)通知目標(biāo)進(jìn)程進(jìn)程內(nèi)存釋放午绳。這包括調(diào)用目標(biāo)進(jìn)程的scheduleLowMemory()方法和processInBackground()方法。
關(guān)閉Activity的三種情況
- 第一種稚铣,從調(diào)用startActivity()開(kāi)始箱叁,一般情況下墅垮,當(dāng)前都有正在運(yùn)行的Activity,所以需要先暫停當(dāng)前的Activity耕漱,而暫停完畢后算色,AmS會(huì)收到一個(gè)Binder消息,并開(kāi)始從completePaused()處執(zhí)行螟够。在該函數(shù)中灾梦,由于上一個(gè)Activity并沒(méi)有finishing,僅僅是stop妓笙,所以這里會(huì)把上一個(gè)Activity添加到mStoppingActivity列表中若河。當(dāng)目標(biāo)Activity啟動(dòng)后,會(huì)向Ams發(fā)送一個(gè)請(qǐng)求進(jìn)行內(nèi)存回收的消息寞宫,這會(huì)導(dǎo)致AmS在內(nèi)部調(diào)用activityIdleInternal()方法萧福,該方法中首先會(huì)處理mStoppingActivities列表中的Activity,這就會(huì)調(diào)用stopActivityLocked()方法辈赋。這又會(huì)通過(guò)IPC調(diào)用鲫忍,通知應(yīng)用進(jìn)程stop指定的Activity,當(dāng)stop完畢后钥屈,再報(bào)告給AmS悟民,于是AmS再?gòu)腶ctivityStopped()出開(kāi)始執(zhí)行,而這會(huì)調(diào)用trimApplication()方法篷就,該方法會(huì)執(zhí)行內(nèi)存相關(guān)的操作射亏。
- 第二種,當(dāng)按Back鍵后竭业,會(huì)調(diào)用finishActivityLocked()智润,然后把該Activity的finishing標(biāo)識(shí)設(shè)為true,然后再調(diào)用startPausingLocked()永品,當(dāng)目標(biāo)Activity完成暫停后做鹰,就會(huì)報(bào)告AmS,此時(shí)AmS又會(huì)從completePaused()處開(kāi)始執(zhí)行鼎姐。與第一種情況不同钾麸,由于此時(shí)暫停的Activity的finishing狀態(tài)已經(jīng)設(shè)置為true,所以會(huì)執(zhí)行finishingActivityLocked()炕桨,而不是像第一種情況中僅僅把該Activity添加到mStoppingActivities列表饭尝。
- 第三種,當(dāng)Activity啟動(dòng)后献宫,會(huì)向AmS發(fā)送一個(gè)Idle消息钥平,這會(huì)導(dǎo)致AmS開(kāi)始執(zhí)行activityIdleInternal()方法。該方法會(huì)首先處理mStoppingActivities列表中的對(duì)象姊途,接著處理mFinishingActivities列表涉瘾,最后再調(diào)用trimApplication()方法知态。
以上就是關(guān)閉Activity的三種情況,包括stop和destory立叛,客戶(hù)進(jìn)程中與之對(duì)應(yīng)的就是onStop()和onDestory()的調(diào)用负敏。
如果使用OOM還有AmS機(jī)制殺死后臺(tái)進(jìn)程后,此時(shí)運(yùn)行的Activity數(shù)量依然超過(guò)MAX_ACTIVITIES(20)秘蛇,則需要繼續(xù)銷(xiāo)毀滿(mǎn)足以下三個(gè)條件的Activity:
- Activity必須已經(jīng)stop其做,但卻沒(méi)有finishing
- 必須是不可見(jiàn)的,既該Activity窗口上面有其他全屏的窗口赁还,如果不是全屏妖泄,則后面的Activity是可見(jiàn)的。
- 不能是persistent類(lèi)型艘策,既常駐進(jìn)程不能被殺死蹈胡。
進(jìn)程優(yōu)先級(jí)
Android系統(tǒng)試圖盡可能長(zhǎng)時(shí)間地保持應(yīng)用程序進(jìn)程,但為了新建或者運(yùn)行更加重要的進(jìn)程朋蔫,總是需要清除過(guò)時(shí)進(jìn)程來(lái)回收內(nèi)存审残。為了決定保留或終止哪個(gè)進(jìn)程,根據(jù)進(jìn)程內(nèi)運(yùn)行的組件及這些組件的狀態(tài)斑举,系統(tǒng)把每個(gè)進(jìn)程都劃入一個(gè)“重要性層次結(jié)構(gòu)”中。重要性最低的進(jìn)程首先會(huì)被清除病涨,然后是下一個(gè)最低的富玷,依此類(lèi)推。
重要性層次結(jié)構(gòu)共有5級(jí)既穆,以下列表按照重要程度列出了各類(lèi)進(jìn)程(第一類(lèi)進(jìn)程是最重要的赎懦,將最后一個(gè)被終止):
1)前臺(tái)進(jìn)程
用戶(hù)當(dāng)前操作所必須的進(jìn)程。滿(mǎn)足以下任一條件時(shí)幻工,進(jìn)程被視作處于前臺(tái):
其中運(yùn)行著正與用戶(hù)交互的Activity(Activity對(duì)象的onResume()方法已被調(diào)用)励两。
其中運(yùn)行著與用戶(hù)交互的activity綁定的Service。
其中運(yùn)行著前臺(tái)Service囊颅,既該Service以startForeground()方式被調(diào)用当悔。
其中運(yùn)行著正在執(zhí)行生命周期回調(diào)方法(onCreate()、onStart()或onDestory())的Service踢代。
其中運(yùn)行著正在執(zhí)行onReceive()方法的BroadcastReceiver盲憎。
一般而言,任何時(shí)刻前臺(tái)進(jìn)程的數(shù)量都為數(shù)不多胳挎,只有當(dāng)內(nèi)存不足以維持它們同時(shí)運(yùn)行時(shí)才會(huì)被終止饼疙。通常,設(shè)備這時(shí)候已經(jīng)到了使用虛擬內(nèi)存的地步慕爬,終止一些前臺(tái)進(jìn)程是為了保證用戶(hù)界面的及時(shí)響應(yīng)窑眯。
2) 可見(jiàn)進(jìn)程
沒(méi)有前臺(tái)組件屏积、但仍會(huì)影響用戶(hù)在屏幕上所見(jiàn)內(nèi)容的進(jìn)程。滿(mǎn)足以下任一條件時(shí)磅甩,進(jìn)程被認(rèn)為是可見(jiàn)的:
其中運(yùn)行著非前臺(tái)Activity炊林,但用戶(hù)仍然可見(jiàn)到此activity(onPause()方法被調(diào)用)。例如更胖,打開(kāi)了一個(gè)對(duì)話(huà)框铛铁,而activity還允許顯示在對(duì)話(huà)框后面,對(duì)用戶(hù)依然可見(jiàn)却妨。
其中運(yùn)行著被可見(jiàn)(或前臺(tái))activity綁定的Service饵逐。
可見(jiàn)進(jìn)程被認(rèn)為是非常重要的進(jìn)程,除非無(wú)法維持所有前臺(tái)進(jìn)程同時(shí)運(yùn)行了彪标,否則它們是不會(huì)被終止的倍权。
3) 服務(wù)進(jìn)程
此進(jìn)程運(yùn)行著由startService()方法啟動(dòng)的服務(wù),它不會(huì)升級(jí)為前臺(tái)進(jìn)程或可見(jiàn)進(jìn)程捞烟。盡管服務(wù)進(jìn)程不直接和用戶(hù)所見(jiàn)內(nèi)容關(guān)聯(lián)薄声,但他們通常在執(zhí)行一些用戶(hù)關(guān)心的操作(比如在后臺(tái)播放音樂(lè)或從網(wǎng)絡(luò)下載數(shù)據(jù))。因此题画,除非內(nèi)存不足以維持所有前臺(tái)默辨、可見(jiàn)進(jìn)程同時(shí)運(yùn)行,系統(tǒng)會(huì)保持服務(wù)進(jìn)程的運(yùn)行苍息。
4) 后臺(tái)進(jìn)程
包含用戶(hù)不可見(jiàn)activity(Activity對(duì)象的onStop()方法已被調(diào)用)的進(jìn)程缩幸。這些進(jìn)程對(duì)用戶(hù)體驗(yàn)沒(méi)有直接的影響,系統(tǒng)可能在任意時(shí)間終止它們竞思,以回收內(nèi)存供前臺(tái)進(jìn)程表谊、可見(jiàn)進(jìn)程及服務(wù)進(jìn)程使用。
通常系統(tǒng)會(huì)有很多后臺(tái)進(jìn)程在運(yùn)行盖喷,所以它們被保存在一個(gè)LRU(最近最少使用)列表中爆办,以確保最近被用戶(hù)使用的activity最后一個(gè)被終止。如果一個(gè)activity正確實(shí)現(xiàn)了生命周期方法课梳,并保存了當(dāng)前的狀態(tài)距辆,則終止此類(lèi)進(jìn)程不會(huì)對(duì)用戶(hù)體驗(yàn)產(chǎn)生可見(jiàn)的影響。因?yàn)樵谟脩?hù)返回時(shí)暮刃,activity會(huì)恢復(fù)所有可見(jiàn)的狀態(tài)挑格。關(guān)于保存和恢復(fù)狀態(tài)的詳細(xì)信息,請(qǐng)參閱Activity文檔沾歪。
5) 空進(jìn)程
不含任何活動(dòng)應(yīng)用程序組件的進(jìn)程漂彤。保留這種進(jìn)程的唯一目的就是用作緩存,以改善下次在此進(jìn)程中運(yùn)行組件的啟動(dòng)時(shí)間。為了在進(jìn)程緩存和內(nèi)核緩存間平衡系統(tǒng)整體資源挫望,系統(tǒng)經(jīng)常會(huì)終止這種進(jìn)程立润。
依據(jù)進(jìn)程中目前活躍組件的重要程度,Android會(huì)給進(jìn)程評(píng)估一個(gè)盡可能高的級(jí)別媳板。例如桑腮,如果一個(gè)進(jìn)程中運(yùn)行著一個(gè)服務(wù)和一個(gè)用戶(hù)可見(jiàn)的activity,則此進(jìn)程會(huì)被評(píng)定為可見(jiàn)進(jìn)程蛉幸,而不是服務(wù)進(jìn)程破讨。
此外,一個(gè)進(jìn)程的級(jí)別可能會(huì)由于其它進(jìn)程的依賴(lài)而被提高——為其它進(jìn)程提供服務(wù)的進(jìn)程級(jí)別永遠(yuǎn)不會(huì)低于使用此服務(wù)的進(jìn)程奕纫。比如:如果A進(jìn)程中的content provider為進(jìn)程B中的客戶(hù)端提供服務(wù)提陶,或進(jìn)程A中的服務(wù)被進(jìn)程B中的組件所調(diào)用,則A進(jìn)程至少被視為與進(jìn)程B同樣重要匹层。
因?yàn)檫\(yùn)行服務(wù)的進(jìn)程級(jí)別是高于后臺(tái)activity進(jìn)程的隙笆,所以,如果activity需要啟動(dòng)一個(gè)長(zhǎng)時(shí)間運(yùn)行的操作升筏,則為其啟動(dòng)一個(gè)服務(wù)會(huì)比簡(jiǎn)單地創(chuàng)建一個(gè)工作線程更好些——尤其是該操作時(shí)間比activity的生存期還要長(zhǎng)的情況下撑柔。比如,一個(gè)activity要把圖片上傳至Web網(wǎng)站您访,就應(yīng)該創(chuàng)建一個(gè)服務(wù)來(lái)執(zhí)行之铅忿,即使用戶(hù)離開(kāi)了此activity,上傳還是會(huì)在后臺(tái)繼續(xù)運(yùn)行灵汪。不論activity發(fā)生什么情況辆沦,使用服務(wù)可以保證操作至少擁有“服務(wù)進(jìn)程”的優(yōu)先級(jí)。同理识虚,廣播接收器broadcast receiver也是使用服務(wù)來(lái)處理耗時(shí)任務(wù)的,而不是簡(jiǎn)單地把它放入線程中妒茬。
殺不死的Service
如何讓?xiě)?yīng)用在手機(jī)中存活更長(zhǎng)時(shí)間担锤?網(wǎng)上各種方法可謂是千奇百怪,有些簡(jiǎn)直異想天開(kāi)乍钻。
- 系統(tǒng)廣播喚醒應(yīng)用肛循,比如手機(jī)開(kāi)機(jī),網(wǎng)絡(luò)切換等
- 接入第三方SDK喚醒應(yīng)用银择,比如接入微信SDK會(huì)喚醒微信
- 免殺白名單多糠,比如360免殺白名單,MIUI系統(tǒng)免殺白名單
- 全家桶浩考,應(yīng)用之間互相喚醒夹孔,比如百度系,阿里系應(yīng)用
- 兩個(gè)Service互相喚醒(這個(gè)就別想了,不靠譜)
- 使用Timer定時(shí)器(一樣不靠譜)
設(shè)計(jì)良好的應(yīng)用不應(yīng)該在用戶(hù)不使用的時(shí)候依然保持運(yùn)行搭伤。一直在后臺(tái)運(yùn)行不光費(fèi)電費(fèi)流量只怎,還是造成系統(tǒng)卡頓的主要原因之一(參見(jiàn)上文分析)。正常的做法是優(yōu)化你的應(yīng)用程序怜俐,減少不合理場(chǎng)景的情況身堡,除一些必要服務(wù)應(yīng)用外,大部分應(yīng)用不需要一直在后臺(tái)保存運(yùn)行狀態(tài)拍鲤。
有正常的做法就有不正常的做法贴谎,讓?xiě)?yīng)用長(zhǎng)時(shí)間停留在用戶(hù)手機(jī)中無(wú)外乎就是增加所謂的活躍用戶(hù)數(shù)等一些產(chǎn)品指標(biāo)。這對(duì)于很多公司還是很有吸引力的季稳。
如上文所說(shuō)擅这,無(wú)論應(yīng)用怎么掙扎,當(dāng)處于不可見(jiàn)進(jìn)程的情況下隨時(shí)都有可能被殺死绞幌。所以使用前臺(tái)進(jìn)程是最有效的方法蕾哟。但前臺(tái)進(jìn)程必須有一個(gè)Notifcation顯示在通知欄中,有沒(méi)有辦法讓?xiě)?yīng)用以前臺(tái)進(jìn)程的方式啟動(dòng)同時(shí)又不顯示Notifcation莲蜘?方法當(dāng)然有谭确,就是利用系統(tǒng)漏洞:
- API<18,啟動(dòng)前臺(tái)Service時(shí)直接傳入new Notifcation();
- API>=18票渠,同時(shí)啟動(dòng)兩個(gè)id相同的前臺(tái)Service逐哈,然后再將后啟動(dòng)的Service做stop處理
目前,QQ问顷,微信昂秃,支付寶等知名應(yīng)用都使用此方案。不過(guò)如果應(yīng)用占用太多內(nèi)存即使是前臺(tái)進(jìn)程也依然會(huì)被干掉杜窄。
這些所謂的實(shí)現(xiàn)進(jìn)程殺不死的方案并不都是一勞永逸的方法肠骆,以犧牲用戶(hù)體驗(yàn)為代價(jià)很有可能會(huì)激怒用戶(hù)卸載你的應(yīng)用,所以最好的方式還是遵循Android規(guī)范開(kāi)發(fā)性能更優(yōu)更合理的應(yīng)用程序塞耕。