android 內存管理之我見

​​​​​​Android是一個基於Linux實現的操作系統。但對於Linux內核來說,Android也僅僅只是一個運行在內核之上的應用程序,與其他運行在內核之上的應用程序沒有任何區別。所以Android需要一套機制管理運行在Linux進程中的APK應用程序。Android內存管理包含兩部分,一部分是Framework對內存的管理,一部分是Linux內核對內存管理,這兩部分共同決定應用程序的生命週期。本文主要闡述Android內存管理機制的實現原理,以及在應用開發中需要注意的一些事項,最後本文總結了如何實現殺不死進程的一種方法。

Linux 進程回收


在Android中,大部分應用程序都運行在一個獨立的Linux進程中,每個進程都有獨立的內存空間。隨著各種應用程序啟動,系統內存不斷下降,為了保證新應用能夠運行,Android需要一套機制殺死暫時閒置的進程。

Android Framework並不能直接回收內存,其管理進程的服務(ActivityManagerService,以下簡稱AmS)也同應用程序一樣運行在Java虛擬機環境裡。Java虛擬機都運行在各自獨立的內存空間,所以ActivityManagerService沒有辦法感知應用程序是否OOM。

Android系統中還運行了一個OOM進程。該進程啟動時首先會在Linux內核中把自己註冊為一個OOM Killer。AmS需要把每一個應用程序的oom_adj值告知OOM Killer,這個值的範圍在-16到15之間,值越低,說明越重要,這個值類似於Linux中的nice值,只在標準的Linux中,有其自己的OOM Killer。Android中的OOM Killer進程僅僅適用於Android應用程序。

當內核的內存管理模塊檢測到系統內存不足時就會通知OOM Killer,然後OOM Killer根據AmS所告知的優先級強制退出優先級低的應用程序。

應用程序在內存中的狀態


Android官方聲稱,Activity退出後,其所在進程並不會被立即殺死,從而在下次啟動Activity時,能夠提高啟動速度。這些Activity只有在內存緊張時才會被系統殺死。所以對於應用程序來說,關閉並不意味著釋放內存。

Activity在內存中的狀態

系統只有一個Activity處於與用戶交互的狀態,對於非交互狀態的Activity,AmS會在內部暫時緩存起來而不是立即殺死,但如果後臺Activity數目超過一定閾值,AmS則會強制殺死一些優先級低的Activity。以下是Activity在內存或者說在AmS中的狀態:

AmS會記錄最近啟動的20個Activity,如果超過20則捨棄最早記錄的Activity。

AmS會將所有正在運行的Activity保存在一個列表中,對於使用back返回的Activity則從列表中清除。

AmS使用Lru算法保存所有最近使用過的Activity。

AmS使用一個列表(mStoppingActivities)保存需要停止的Activity,這種情況

發生在啟動一個Activity時,AmS遵循先啟動後停止的策略,將需要停止的Activity保存在此列表中,等AmS閒置下來後再停止Activity。

AmS使用一個列表保存處於finish狀態(onDestory())的Activity,當一個Activity處於finish狀態時(onDestory()執行後)不會被立即殺死,而是保存到該列表中直到超過系統設定的警戒線才會回收該列表中的Activity。

應用進程在內存中的狀態

每個應用程序都對應著一個ActivityThread類,該類初始化後就進入Looper.loop()函數中無限循環。​

android 內存管理之我見

​以後則依靠消息機制運行,既當有消息時處理消息,沒有消息則應用進程進入sleep狀態。loop()方法內部代碼如下所示:​

android 內存管理之我見

​在Linux內核調度中,如果一個線程的狀態為sleep,則除了佔用調度本身的時間,不會佔用CPU時間片。

有三種情況會喚醒應用線程,一種是定時器中斷(比如我們設置的鬧鐘,在程序中可以設置定時任務),第二種是用戶按鍵消息,第三種是Binder消息(Binder用於進程間通信,其在應用程序中會自動創建一個線程,Binder在接收到消息後會想UI主線程發送一個消息從而使queue.next()繼續執行)這就是所謂的消息驅動模式。

所以設計良好的應用程序當處於後臺時不會佔用任何CPU時間,更不會拖慢系統運行速度。其所佔用的僅僅是內存,即使釋放所佔用的內存也不會提高系統運行速度。當然這裡說的是設計良好的應用程序,目前國內很多應用在處於後臺狀態時依然會偷偷幹很多事情,這無疑就拖慢了系統運行速度。

Android 內存回收


Activity所佔內存在一般情況下不會被回收,只有在系統內存不夠用時才會回收,並且回收會遵循一定規則。大致可以概括為前臺Activity最後回收,其次是包含前臺的Service或者Provider,再其次是後臺Activity,最後是空進程。

內存釋放的三個地方

第一個是在ActivityManagerService中運行,即Android所聲稱的當系統內存低時,優先釋放沒有任何Activity的進程,然後釋放非前臺Activity對應的進程。

第二個是在OOM Killer中,此時AmS只要告訴OOM各個應用的優先級,然後OOM就會調用Linux內部的進程管理方法殺死優先級較低的進程。

第三個是在應用進程本身之中,當AmS認為目標進程需要被殺死時,首先會通知目標進程進程內存釋放。這包括調用目標進程的scheduleLowMemory()方法和processInBackground()方法。

關閉Activity的三種情況

第一種,從調用startActivity()開始,一般情況下,當前都有正在運行的Activity,所以需要先暫停當前的Activity,而暫停完畢後,AmS會收到一個Binder消息,並開始從completePaused()處執行。在該函數中,由於上一個Activity並沒有finishing,僅僅是stop,所以這裡會把上一個Activity添加到mStoppingActivity列表中。當目標Activity啟動後,會向Ams發送一個請求進行內存回收的消息,這會導致AmS在內部調用activityIdleInternal()方法,該方法中首先會處理mStoppingActivities列表中的Activity,這就會調用stopActivityLocked()方法。這又會通過IPC調用,通知應用進程stop指定的Activity,當stop完畢後,再報告給AmS,於是AmS再從activityStopped()出開始執行,而這會調用trimApplication()方法,該方法會執行內存相關的操作。

第二種,當按Back鍵後,會調用finishActivityLocked(),然後把該Activity的finishing標識設為true,然後再調用startPausingLocked(),當目標Activity完成暫停後,就會報告AmS,此時AmS又會從completePaused()處開始執行。與第一種情況不同,由於此時暫停的Activity的finishing狀態已經設置為true,所以會執行finishingActivityLocked(),而不是像第一種情況中僅僅把該Activity添加到mStoppingActivities列表。

第三種,當Activity啟動後,會向AmS發送一個Idle消息,這會導致AmS開始執行activityIdleInternal()方法。該方法會首先處理mStoppingActivities列表中的對象,接著處理mFinishingActivities列表,最後再調用trimApplication()方法。

以上就是關閉Activity的三種情況,包括stop和destory,客戶進程中與之對應的就是onStop()和onDestory()的調用。

如果使用OOM還有AmS機制殺死後臺進程後,此時運行的Activity數量依然超過MAX_ACTIVITIES(20),則需要繼續銷燬滿足以下三個條件的Activity:

Activity必須已經stop,但卻沒有finishing

必須是不可見的,既該Activity窗口上面有其他全屏的窗口,如果不是全屏,則後面的Activity是可見的。

不能是persistent類型,既常駐進程不能被殺死。

進程優先級


Android系統試圖儘可能長時間地保持應用程序進程,但為了新建或者運行更加重要的進程,總是需要清除過時進程來回收內存。為了決定保留或終止哪個進程,根據進程內運行的組件及這些組件的狀態,系統把每個進程都劃入一個“重要性層次結構”中。重要性最低的進程首先會被清除,然後是下一個最低的,依此類推。

重要性層次結構共有5級,以下列表按照重要程度列出了各類進程(第一類進程是最重要的,將最後一個被終止):

1)前臺進程

用戶當前操作所必須的進程。滿足以下任一條件時,進程被視作處於前臺:

其中運行著正與用戶交互的Activity(Activity對象的onResume()方法已被調用)。

其中運行著與用戶交互的activity綁定的Service。

其中運行著前臺Service,既該Service以startForeground()方式被調用。

其中運行著正在執行生命週期回調方法(onCreate()、onStart()或onDestory())的Service。

其中運行著正在執行onReceive()方法的BroadcastReceiver。

一般而言,任何時刻前臺進程的數量都為數不多,只有當內存不足以維持它們同時運行時才會被終止。通常,設備這時候已經到了使用虛擬內存的地步,終止一些前臺進程是為了保證用戶界面的及時響應。

2) 可見進程

沒有前臺組件、但仍會影響用戶在屏幕上所見內容的進程。滿足以下任一條件時,進程被認為是可見的:

其中運行著非前臺Activity,但用戶仍然可見到此activity(onPause()方法被調用)。例如,打開了一個對話框,而activity還允許顯示在對話框後面,對用戶依然可見。

其中運行著被可見(或前臺)activity綁定的Service。

可見進程被認為是非常重要的進程,除非無法維持所有前臺進程同時運行了,否則它們是不會被終止的。

3) 服務進程

此進程運行著由startService()方法啟動的服務,它不會升級為前臺進程或可見進程。儘管服務進程不直接和用戶所見內容關聯,但他們通常在執行一些用戶關心的操作(比如在後臺播放音樂或從網絡下載數據)。因此,除非內存不足以維持所有前臺、可見進程同時運行,系統會保持服務進程的運行。

4) 後臺進程

包含用戶不可見activity(Activity對象的onStop()方法已被調用)的進程。這些進程對用戶體驗沒有直接的影響,系統可能在任意時間終止它們,以回收內存供前臺進程、可見進程及服務進程使用。

通常系統會有很多後臺進程在運行,所以它們被保存在一個LRU(最近最少使用)列表中,以確保最近被用戶使用的activity最後一個被終止。如果一個activity正確實現了生命週期方法,並保存了當前的狀態,則終止此類進程不會對用戶體驗產生可見的影響。因為在用戶返回時,activity會恢復所有可見的狀態。關於保存和恢復狀態的詳細信息,請參閱Activity文檔。

5) 空進程

不含任何活動應用程序組件的進程。保留這種進程的唯一目的就是用作緩存,以改善下次在此進程中運行組件的啟動時間。為了在進程緩存和內核緩存間平衡系統整體資源,系統經常會終止這種進程。

依據進程中目前活躍組件的重要程度,Android會給進程評估一個儘可能高的級別。例如,如果一個進程中運行著一個服務和一個用戶可見的activity,則此進程會被評定為可見進程,而不是服務進程。

此外,一個進程的級別可能會由於其它進程的依賴而被提高——為其它進程提供服務的進程級別永遠不會低於使用此服務的進程。比如:如果A進程中的content provider為進程B中的客戶端提供服務,或進程A中的服務被進程B中的組件所調用,則A進程至少被視為與進程B同樣重要。

因為運行服務的進程級別是高於後臺activity進程的,所以,如果activity需要啟動一個長時間運行的操作,則為其啟動一個服務會比簡單地創建一個工作線程更好些——尤其是該操作時間比activity的生存期還要長的情況下。比如,一個activity要把圖片上傳至Web網站,就應該創建一個服務來執行之,即使用戶離開了此activity,上傳還是會在後臺繼續運行。不論activity發生什麼情況,使用服務可以保證操作至少擁有“服務進程”的優先級。同理,廣播接收器broadcast receiver也是使用服務來處理耗時任務的,而不是簡單地把它放入線程中。

殺不死的Service


如何讓應用在手機中存活更長時間?網上各種方法可謂是千奇百怪,有些簡直異想天開。

系統廣播喚醒應用,比如手機開機,網絡切換等

接入第三方SDK喚醒應用,比如接入微信SDK會喚醒微信

免殺白名單,比如360免殺白名單,MIUI系統免殺白名單

全家桶,應用之間互相喚醒,比如百度系,阿里系應用

兩個Service互相喚醒(這個就別想了,不靠譜)

使用Timer定時器(一樣不靠譜)

設計良好的應用不應該在用戶不使用的時候依然保持運行。一直在後臺運行不光費電費流量,還是造成系統卡頓的主要原因之一(參見上文分析)。正常的做法是優化你的應用程序,減少不合理場景的情況,除一些必要服務應用外,大部分應用不需要一直在後臺保存運行狀態。

有正常的做法就有不正常的做法,讓應用長時間停留在用戶手機中無外乎就是增加所謂的活躍用戶數等一些產品指標。這對於很多公司還是很有吸引力的。

如上文所說,無論應用怎麼掙扎,當處於不可見進程的情況下隨時都有可能被殺死。所以使用前臺進程是最有效的方法。但前臺進程必須有一個Notifcation顯示在通知欄中,有沒有辦法讓應用以前臺進程的方式啟動同時又不顯示Notifcation?方法當然有,就是利用系統漏洞:

API<18,啟動前臺Service時直接傳入new Notifcation();

API>=18,同時啟動兩個id相同的前臺Service,然後再將後啟動的Service做stop處理

目前,QQ,微信,支付寶等知名應用都使用此方案。不過如果應用佔用太多內存即使是前臺進程也依然會被幹掉。

這些所謂的實現進程殺不死的方案並不都是一勞永逸的方法,以犧牲用戶體驗為代價很有可能會激怒用戶卸載你的應用,所以最好的方式還是遵循Android規範開發性能更優更合理的應用程序。


分享到:


相關文章: