Android對Linux系統的內存管理機制進行的優化


由於你的分享、點贊、在看
我足足的精氣神兒!

Android對內存的使用方式一樣是「盡最大限度的使用」,這一點繼承了Linux的優勢。只不過有所不一樣的是,Linux側重於儘量多的緩存磁盤數據以下降磁盤IO進而提升系統的數據訪問性能,而 Android側重於儘量多的緩存進程以提升應用啓動和切換速度。Linux系統在進程活動中止後就結束該進程,而Android系統則會在內存中儘可能長時間的保持應用進程,直到系統須要更多內存爲止 。這些保留在內存中的進程,一般狀況下不會影響系統總體運行速度,反而會在用戶再次激活這些進程時,加快進程的啓動速度,由於不用從新加載界面資源了,這是Android標榜的特性之一。因此,Android如今不推薦顯式的「退出」應用。web

那爲何內存少的時候運行大型程序會慢呢,緣由是:在內存剩餘很少時打開大型程序會觸發系統自身的進程調度策略,這是十分消耗系統資源的操做,特別是在一個程序頻繁向系統申請內存的時候。這種狀況下系統並不會關閉全部打開的進程,而是選擇性關閉,頻繁的調度天然會拖慢系統。算法

Android中的進程管理

說到Android的內存管理,就不得不提到進程管理,由於進程管理確確切切的影響着系統內存。在瞭解進程管理以前,咱們首先了解一些基礎概念。設計模式

當某個應用組件啓動且該應用沒有運行其餘任何組件時,Android 系統會使用單個執行線程爲應用啓動新的 Linux 進程。默認狀況下,同一應用的全部組件在相同的進程和線程(稱爲「主」線程) 中運行。若是某個應用組件啓動且該應用已存在進程(由於存在該應用的其餘組件),則該組件會在此進程內啓動並使用相同的執行線程。可是,你也能夠安排應用中的其餘組件在單獨的進程中運行,併爲任何進程建立額外的線程。緩存

Android應用模型的設計思想取自Web 2.0的Mashup概念,是 基於組件的應用設計模式。在該模型下,每一個應用都由一系列的組件搭建而成,組件經過應用的配置文件描述功能。Android系統依照組件的配置信息,瞭解各個組件的功能並進行統一調度。這就意味着,來自不一樣應用的組件能夠有機地結合在一塊兒,共同完成任務,各個Android應用,只有明確的組件邊界,而再也不有明確的進程邊界和應用邊界。這種設計,也令得開發者無需耗費精力去從新開發一些附屬功能,而是能夠全身心地投入到核心功能的開發中。這樣不但提升了應用開發的效率,也加強了用戶體驗(好比電子郵件中選擇圖片做爲附件的功能,能夠直接調用專門的圖片應用的功能,不用本身從頭開發)。微信


系統不會爲每一個組件實例建立單獨的線程。運行於同一進程的全部組件均在 UI 線程中實例化,而且對每一個組件的系統調用均由該線程進行分派。 所以,響應系統回調的方法(例如,報告用戶操做的 onKeyDown() 或生命週期回調方法)始終在進程的 UI 線程中運行(四大組件的各個生命週期回調方法都是在UI線程中觸發的)。網絡

進程的生命週期

Android的一個不尋常的基本特徵是應用程序進程的生命週期並不是是由應用自己直接控制的。相反,進程的生命週期是由系統決定的,系統會權衡每一個進程對用戶的相對重要程度,以及系統的可用內存總量來肯定。 好比說相對於終止一個託管了正在與用戶交互的Activity的進程,系統更可能終止一個託管了屏幕上再也不可見的Activity的進程,不然這種後果是可怕的。所以,是否終止某個進程取決於該進程中所運行組件的狀態 。Android會有限清理那些已經再也不使用的進程,以保證最小的反作用。app

做爲應用開發者,瞭解各個應用組件(特別是Activity、Service和BroadcastReceiver)如何影響應用進程的生命週期很是重要。不正確的使用這些組件,有可能致使系統在應用執行重要工做時終止進程。編輯器

舉個常見的例子, BroadcastReceiver 在其 onReceive() 方法中接收到Intent時啓動一個線程,而後從該函數返回。而一旦返回,系統就認爲該 BroadcastReceiver 再也不處於活動狀態,所以也就再也不須要其託管進程(除非該進程中還有其餘組件處於活動狀態)。這樣一來,系統就有可能隨時終止進程以回收內存,而這也最終會致使運行在進程中的線程被終止。此問題的解決方案一般是從 BroadcastReceiver 中安排一個 JobService ,以便系統知道在該進程中仍有活動的工做。ide

爲了肯定在內存不足時終止哪些進程,Android會根據進程中正在運行的組件以及這些組件的狀態,將每一個進程放入 「重要性層次結構」 中。必要時,系統會首先殺死重要性最低的進程,以此類推,以回收系統資源。這就至關於爲進程分配了優先級的概念。函數

進程優先級

Android中總共有5個進程優先級(按重要性降序):

Foreground Process:前臺進程(正常不會被殺死)

用戶當前操做所必需的進程。有不少組件能以不一樣的方式使得其所在進程被斷定爲前臺進程。若是一個進程知足如下任一條件,即視爲前臺進程:

  • 託管用戶正在交互的 Activity(已調用 Activity 的 onResume() 方法)

  • 託管某個 Service,後者綁定到用戶正在交互的 Activity

  • 託管正執行一個生命週期回調的 Service(onCreate()、onStart() 或 onDestroy())

  • 託管正執行其 onReceive() 方法的 BroadcastReceiver

一般,在任意給定時間前臺進程都爲數很少。只有在內存不足以支持它們同時繼續運行這一萬不得已的狀況下,系統纔會終止它們。此時,設備每每已達到內存分頁狀態,所以須要終止一些前臺進程來確保用戶界面正常響應。

Visible Process:可見進程(正常不會被殺死

沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。殺死這類進程也會明顯影響用戶體驗。若是一個進程知足如下任一條件,即視爲可見進程:

  • 託管不在前臺、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,啓動了一個對話框樣式的前臺 activity ,此時在其後面仍然能夠看到前一個Activity。

運行時權限對話框就屬於此類。考慮一下,還有哪一種狀況會致使只觸發onPause而不觸發onStop?

  • 託管經過 Service.startForeground() 啓動的前臺Service。

Service.startForeground():它要求系統將它視爲用戶可察覺到的服務,或者基本上對用戶是可見的。

  • 託管系統用於某個用戶可察覺的特定功能的Service,好比動態壁紙、輸入法服務等等。

可見進程被視爲是極其重要的進程,除非爲了維持全部前臺進程同時運行而必須終止,不然系統不會終止這些進程。若是這類進程被殺死,從用戶的角度看,這意味着當前 activity 背後的可見 activity 會被黑屏代替。

Service Process:服務進程(正常不會被殺死)

正在運行已使用 startService() 方法啓動的服務且不屬於上述兩個更高類別進程的進程。儘管服務進程與用戶所見內容沒有直接關聯,可是它們一般在執行一些用戶關心的操做(例如,後臺網絡上傳或下載數據)。所以,除非內存不足以維持全部前臺進程和可見進程同時運行,不然系統會讓服務進程保持運行狀態。

已經運行好久(例如30分鐘或更久)的Service,有可能被降級,這樣一來它們所在的進程就能夠被放入Cached LRU列表中。這有助於避免一些長時間運行的Service因爲內存泄漏或其餘問題而消耗過多的RAM,進而致使系統沒法有效使用緩存進程的狀況。

Background / Cached Process:後臺進程(可能隨時被殺死)

這類進程通常會持有一個或多個目前對用戶不可見的 Activity (已調用 Activity 的 onStop() 方法)。它們不是當前所必須的,所以當其餘更高優先級的進程須要內存時,系統可能 隨時終止 它們以回收內存。但若是正確實現了Activity的生命週期,即使系統終止了進程,當用戶再次返回應用時也不會影響用戶體驗:關聯Activity在新的進程中被從新建立時能夠恢復以前保存的狀態。

在一個正常運行的系統中,緩存進程是內存管理中 惟一 涉及到的進程:一個運行良好的系統將始終具備多個緩存進程(爲了更高效的切換應用),並根據須要按期終止最舊的進程。只有在很是嚴重(而且不可取)的狀況下,系統纔會到達這樣一個點,此時全部的緩存進程都已被終止,而且必須開始終止服務進程。

Android系統回收後臺進程的參考條件

  • LRU算法:自下而上開始終止,先回收最老的進程。越老的進程近期內被用戶再次使用的概率越低。殺死的進程越老,對用戶體驗的影響就越小。

  • 回收收益:系統老是傾向於殺死一個能回收更多內存的進程,由於在它被殺時會爲系統提供更多內存增益,從而能夠殺死更少的進程。殺死的進程越少,對用戶體驗的影響就越小。換句話說,應用進程在整個LRU列表中消耗的內存越少,保留在列表中而且可以快速恢復的機會就越大。

這類進程會被保存在一個僞LRU列表中,系統會優先殺死處於列表尾部(最老)的進程,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。這個LRU列表排序的確切策略是平臺的實現細節,但一般狀況下,相對於其餘類型的進程,系統會優先嚐試保留更有用的進程(好比託管用戶主應用程序的進程,或者託管用戶看到的最後一個Activity的進程,等等)。還有其餘一些用於終止進程的策略:對容許的進程數量硬限制,對進程能夠持續緩存的時間量的硬限制,等等。

在一個健康的系統中,只有緩存進程或者空進程會被系統隨時終止,若是服務進程,或者更高優先級的可見進程以及前臺進程也開始被系統終止(不包括應用自己糟糕的內存使用致使OOM),那就說明系統運行已經處於一個亞健康甚至極不健康的狀態,可用內存已經吃緊。

Empty Process:空進程(能夠隨時殺死)

不含任何活躍組件的進程。保留這種進程的的惟一目的是用做緩存(爲了更加有效的使用內存而不是徹底釋放掉),以縮短下次啓動應用程序所需的時間,由於啓動一個新的進程也是須要代價的。只要有須要,Android會隨時殺死這些進程。

內存管理中對於前臺/後臺應用的定義,與用於Service限制目的的後臺應用定義不一樣。從Android 8.0開始,出於節省系統資源、優化用戶體驗、提升電池續航能力的考量,系統進行了前臺/後臺應用的區分,對於後臺service進行了一些限制。在該定義中,若是知足如下任意條件,應用將被視爲處於前臺:

>

  • 具備可見 Activity(無論該 Activity 已啓動仍是已暫停)。

  • 具備前臺 Service。

  • 另外一個前臺應用已關聯到該應用(無論是經過綁定到其中一個 Service,仍是經過使用其中一個內容提供程序)。例如,若是另外一個應用綁定到該應用的 Service,那麼該應用處於前臺:IME 壁紙 Service 通知偵聽器 語音或文本 Service 若是以上條件均不知足,應用將被視爲處於後臺。

Android系統如何評定進程的優先級

根據進程中當前活動組件的重要程度,Android 會將進程評定爲它可能達到的最高級別。 例如,若是某進程同時託管着 Service 和可見 Activity,則會將此進程評定爲可見進程,而不是服務進程。

此外,一個進程的級別可能會因其餘進程對它的依賴而有所提升,即服務於另外一進程的進程其級別永遠不會低於其所服務的進程。 例如,若是進程 A 中的內容提供程序爲進程 B 中的客戶端提供服務,或者若是進程 A 中的服務綁定到進程 B 中的組件,則進程 A 始終被視爲至少與進程 B 一樣重要。

因爲運行服務的進程其級別高於託管後臺 Activity 的進程,所以,在 Activity 中啓動一個長時間運行的操做時,最好爲該操做啓動服務,而不是簡單地建立工做線程,當操做有可能比 Activity 更加持久時尤要如此。例如,一個文件上傳的操做就能夠考慮使用服務來完成,這樣一來,即便用戶退出 Activity,仍可在後臺繼續執行上傳操做。使用服務能夠保證,不管 Activity 發生什麼狀況,該操做至少具有「服務進程」優先級。同理, BroadcastReceiver 也應使用服務,而不是簡單地將耗時冗長的操做放入線程中。

Home鍵退出和返回鍵退出的區別

Home鍵退出,程序保留狀態爲後臺進程;而返回鍵退出,程序保留狀態爲空進程,空進程更容易被系統回收。Home鍵其實主要用於進程間切換,返回鍵則是真正的退出程序。

從理論上來說,不管是哪一種狀況,在沒有任何後臺工做線程(即使應用處於後臺,工做線程仍然能夠執行)的前提下,被置於後臺的進程都只是保留他們的運行狀態,並不會佔用CPU資源,因此也不耗電。只有音樂播放軟件之類的應用須要在後臺運行Service,而Service是須要佔用CPU時間的,此時纔會耗電。因此說沒有帶後臺服務的應用是不耗電也不佔用CPU時間的,不必關閉,這種設計自己就是Android的優點之一,可讓應用下次啓動時更快。然而現實是,不少應用多多少少都會有一些後臺工做線程,這多是開發人員經驗不足致使(好比線程未關閉或者循環發送的Handler消息未中止),也多是爲了需求而有意爲之,致使整個Android應用的生態環境並非一片乾淨。




夯實基礎,關注前沿,娛樂生活

掌握更多前沿技術,獲取更多笑點 

請關注--------喘口仙氣



本文分享自微信公衆號 - 喘口仙氣(gh_db8538619cdd)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索