1. 程式人生 > >Android 記憶體管理

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()函式中無限迴圈。

Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
Looper.loop();

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

public static final loop() {
  Looper me = myLooper();
  MessageQueue queue = me.mQueue;
  while(true){
    Message msg = queue.next();// might block
    ...
  }
}

在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:

  1. Activity必須已經stop,但卻沒有finishing
  2. 必須是不可見的,既該Activity視窗上面有其他全屏的視窗,如果不是全屏,則後面的Activity是可見的。
  3. 不能是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規範開發效能更優更合理的應用程式。

相關推薦

Android 記憶體管理記錄

專案中用到大量大圖,造成快速切換Activity後記憶體不足,如登入介面用到3M高清大圖,裝置選擇用到5張大圖背景疊加效果,主介面用到了5張遮罩大圖,設定介面總的子activity中也有大圖出現。 啟動Splash——裝置選擇介面(保留棧低,不finish,但會把背景圖片回收)-->主介面-

android 記憶體管理以及優化 粗略方案

Android的記憶體管理方式 1.android系統記憶體分配和回收方式 一個app通常就是一個程序對應一個虛擬機器 通過adb shell 檢視應用的記憶體分配情況 ①通過ps來檢視系統內的程序 ②通過 dumpsys meminfo 包名  檢視對應的應用的記憶體

Android記憶體管理詳細介紹 native heap dalvikheap 超有用的!!!!!!

尊重原創作者,轉載請註明出處: 最近在網上看了不少Android記憶體管理方面的博文,但是文章大多都是就單個方面去介紹記憶體管理,沒有能全域性把握,缺乏系統性闡述,而且有些觀點有誤,僅僅知道這些,還是無法從整體上理解記憶體管理,對培養系統優化和系統穩定性分析方面的能力是不夠的。     我結合自己的一些思

Android 記憶體管理

Linux 程序回收 在Android中,大部分應用程式都執行在一個獨立的Linux程序中,每個程序都有獨立的記憶體空間。隨著各種應用程式啟動,系統記憶體不斷下降,為了保證新應用能夠執行,Android需要一套機制殺死暫時閒置的程序。 Android Framework並不能直接回收記憶體,其管理程序的服務

android 記憶體管理概要

一、zram zram swap 主要原理就是從記憶體分配一塊區域出來用作 swap 分割槽,每次如果記憶體空間不夠了,不是把應用程式殺掉,而是把應用程式所佔用的記憶體資料複製到 swap 分割槽,等

[譯]Android記憶體管理: 理解App的PSS

Android記憶體管理: 理解App的PSS 當在應用程式上執行Little Eye時,在記憶體檢視中,會報告有關應用程式記憶體的3個重要統計資訊。 Dalvik記憶體使用情況,即Java堆消耗的記憶體量,Native記憶體,即JVM外部程序使用的記憶體

Android記憶體管理機制詳解

無意中在MIUI看到的文章,感覺不錯,轉了過來。 原文如下: 最近看到很多機油發帖抱怨記憶體太小程序殺不掉。首先要表示,這個帖子是從百度貼吧轉來的,主要針對正常的安卓機,像里程碑這種悲劇的小記憶體機器

Android記憶體管理機制和記憶體洩漏分析及優化

Android中的記憶體管理機制 分配機制 Android為每個程序分配記憶體的時候,採用了彈性的分配方式,也就是剛開始並不會一下分配很多記憶體給每個程序,而是給每一個程序分配一個“夠用”的量。這個量是根據每一個裝置實際的實體記憶體大小來決定的。隨著應用

Android面試系列文章2018之記憶體管理之UI卡頓篇

Android面試系列文章2018之記憶體管理之UI卡頓篇 1.UI卡頓的原理   60ftp –> 16ms: Android系統每隔16ms都會對介面進行渲染一次,造成卡頓的原因就是Android系統在渲染的時候丟幀了, 16ms = 1000/60hz,相當於60fps

Android程序的記憶體管理分析

尊重原創作者,轉載請註明出處: 最近在網上看了不少Android記憶體管理方面的博文,但是文章大多都是就單個方面去介紹記憶體管理,沒有能全域性把握,缺乏系統性闡述,而且有些觀點有誤。 這樣對Android記憶體管理進行區域性性介紹,很難使讀者建立系統性概念,無法真正理解記

Android記憶體優化—Android記憶體管理方式

記憶體管理機制 從作業系統的角度來說,記憶體就是一塊資料儲存區域,屬於可被作業系統排程的資源。現代多工(程序)的作業系統中,記憶體管理尤為重要,作業系統需要為每一個程序合理的分配記憶體資源,所以可以從兩方面來理解作業系統的記憶體管理機制。 第一:分配機制。為每一個程序分配一個合理的記

Android之ION記憶體管理分析

    備註:圖片中的雙向箭頭表示他們是連結串列,前後連結起來的,單向箭頭表示指標指向誰。   做Camera都快2年了,對buffer流轉,buffer queue 等一些細節方面,還是不太明白。雖然也知道怎麼用,但是不知道更深層次的工作機制,內心有點忐忑不安。所以決定拿

Android OOM:記憶體管理分析和記憶體洩露原因總結

一、Android程序的記憶體管理分析 1. 程序的地址空間 在32位作業系統中,程序的地址空間為0到4GB,示意圖如下: 這裡主要說明一下Stack和Heap: Stack空間:(進棧和出棧)由作業系統控制,其中主要儲存 函式地址、函式引數、

Android記憶體程序管理,Low Memory Killer機制

當我們要退出一個程序時只能按返回鍵,而該程序並沒有真正的關閉,程序依然是存在於記憶體之中.這樣設計的目的是為了下次能快速啟動.當然,隨著系統執行時間的增長,記憶體會越來越少,所以系統會定期執行一次檢查,清理一些程序,釋放掉記憶體.這就是Android的Low M

效能優化篇---記憶體管理Android記憶體洩露

記憶體洩漏:當你不再需要某個例項後,但是這個物件卻仍然被引用,防止被垃圾回收。這個情況就叫做記憶體洩露(Memory Leak)。 常見洩漏場景: 1.Handler 導致的記憶體洩漏 12345678910111213141516171819202122 publ

Android記憶體優化六:系統中使用堆和棧管理記憶體的區別

一直對系統中堆和棧的使用原則不太理解,在網上看到這篇文章,非常不錯! 轉載地址:http://bbs.csdn.net/topics/390147637 在計算機領域,堆疊是一個不容忽視的概念,我們編寫的C語言程式基本上都要用到。但對於很多的初學著來說,堆疊是一個很模糊的概

android APP開發的記憶體管理與優化之一 ——LowMemory Killer

從事長期的android APP開發後,有一個開發者都會注意到的問題——記憶體使用。這裡就總結一下我個人開發過程中對於安卓的記憶體管理機制的一些認識,以及一些優化方案。 基於個人的一些開發習慣,我對安卓應用的記憶體機制分三個部分來講述下: 1. LowMemory Kil

Android彈藥庫——記憶體管理機制與程序模型

題外話:喜歡文章的朋友點個贊鼓勵鼓勵唄~ 本篇文章是對 Android 記憶體管理、程序管理的簡單總結,主要偏理論性,但是瞭解箇中原理,對 Android 系統的認知、對高質量程式的編寫、對程式的理解都大有裨益~ Android記憶體管理 Android的記憶體管理哲學 Android 是基於 Linu

Android記憶體管理及優化

轉自  https://www.kotlintc.com/articles/2311?fr=sidebar 一、Android記憶體基礎 實體記憶體與程序記憶體 實體記憶體即移動裝置上的RAM,當啟動一個Android程式時,會啟動一個Dalvik VM程序,系統會

android 記憶體基本原理和機制管理

java語言相對於c/c++語言來說人性化的一點就是java有專門管理回收的垃圾回收器。而c/c++語言只能是“誰造成,誰處理”。 GC 1,GC是垃圾收集的意思(Gabage Collection) 2,Java提供的GC功能可以自動監測物件是否超過