Android后臺駐留:保活和回收的機制

簡介

眾所周知,Android平臺的管理機制下,App進入后臺后,為了提供持續的及時服務(如推送、音樂),或進行駐留獲取收益(跟蹤、信息收集、廣告)等,會利用一些方法來讓自身保持活躍,躲過被Android系統或用戶發覺、清理,實現后臺駐留。

其中,后臺駐留的廣義概念,除了保持在后臺運行外,被其他組件拉起也屬于駐留(喚醒)。

由于駐留會對系統的性能、響應延遲、續航、發熱等帶來負面影響,令系統的資源管理效果降低,屬于違背用戶意愿和知情的惡劣行為,因此將這些App稱為頑固(Diehard)應用,其利用的方法稱為頑固方法。

除了App利用的一些黑科技(甚至是在違法邊緣的擦邊手段)以外,Android系統本身自帶的機制也可以實現保活和拉起。這些保活、拉起機制,粗略劃分為兩類:

  1. 保持活躍,在后臺運行不被清理、回收
  2. 被其他組件喚醒,包括被其他App喚醒、被系統提供的功能喚醒

本文總結上述這兩類會被頑固App利用的機制。

進程和Task管理

首先簡單梳理一下Android Framework層基本的進程管理。

Android平臺基于Linux,除了基于Linux的“進程”維度來進行管理外,還按照Task的概念來管理應用進程,分別為ProcessRecord和TaskRecord。系統可以按Task也可以按Process來管理進程。

Android提供接口直接殺死Linux進程:1. ProcessRecord的kill()方法,其實現是向對應的進程發送SIGNAL_KILL信號;2. libc的kill()函數,也是發送信號

OOM終止進程(LMK)

App進程在系統中根據OOM(Out of Memory)ADJ(Adjustment)級別和進程狀態來確定優先級,當系統需要殺死進程來釋放內存時,優先級越低的會優先終止。OOM ADJ分數越小優先級越高。

由于頑固App進程后臺駐留時可能會被系統回收,因此頑固App通常通過一些手段(services、彈窗)等來降低OOM(提高優先級),減少自身被系統回收的幾率。

最近任務列表結束Task

用戶在多任務界面(Recents)移除應用,系統會結束應用對應的Task:Removing Recent Task Item(RRT)。

該操作會結束掉與Task關聯的進程,但在一些場景下仍然會有對應App的進程沒有被殺死。

  1. 當App通過"Exclude from recents"功能(不在最近任務列表顯示自己)時,沒有提供給用戶結束的機會,就沒有手動結束掉Task的入口
  2. 當一個進程屬于多個Task時(該進程還需要為其他Task服務)

這類終止機制由用戶操作觸發,當頑固應用借助多進程、多任務、喚醒拉起、互拉等操作,被終止后仍在后臺運行(或后續又被喚醒),給用戶感受為“殺不干凈”。

強制結束App

強制結束(Force-Stop)時Android內建的功能,由ActivityManagerService提供接口,可以在設置-應用程序界面由用戶手動調用。

強制結束的范疇是App對應的所有Task(即可以殺死一般App所有進程)。FSA還額外會將App設置為“STOPPED“狀態,禁止應用在下一次被用戶手動啟用或應用跳轉前被廣播、服務等喚醒。強制結束對頑固App的效果不佳,許多頑固App具備Native保活能力、互拉保活、喚醒拉起等對抗措施。

此外,Android提供KILL_BACKGROUND_PROCESSES權限,允許具備權限的App調用API殺死ADJ大于SERVICE_ADJ的后臺進程(即沒有Service的后臺進程可以被殺掉)。

保持活躍或喚醒

從最近任務隱藏或多個最近任務

Android平臺提供的excludeFromRecents功能可以讓App的Task在多任務中隱藏。此外一個進程可以屬于不同的Task,產生多個Task并隱藏其中幾個Task可以實現”殺不干凈“的效果。

提升App進程優先級、阻止部分回收場景

LMK和OOM ADJ會受到進程狀態和優先級的影響,提高優先級可以降低被系統回收的幾率,阻止部分會殺進程的場景。

其中,將借助前臺進程綁定后臺服務進程保活的手段,是較常見的“殺不死、殺不干凈”的情況(最近任務移除后仍有進程)。

  1. 接收廣播,啟動Receiver,具有Receiver的后臺進程優先級高于無Receiver的后臺進程
  2. 創建前臺Service(高版本Android前臺service需要帶有通知),OOM ADJ更低(SERVICE_ADJ),殺死概率更低,此時進程不會被“殺死后臺進程”殺掉(會跳過ADJ小于等于SERVICE_ADJ的進程)
  3. 保持前臺Activity,OOM ADJ更低(用戶可見的Task)
  4. 創建前臺窗口(懸浮窗)或覆蓋窗口(將窗口蓋在前臺App上面)
  5. 將后臺服務綁定到前臺進程,賦予后臺服務在的進程更低的OOM,提升該進程的優先級,減少被殺的幾率;同時對應進程不再屬于后臺進程,不會被“殺死后臺進程”殺死,且該進程轉為“需要為其他Task服務”,同樣不會被最近任務移除時殺死
  6. 對于涉及Service的場景,ContentProvider也適用

借助Sticky Service喚醒

黏性Service是系統提供的機制,被殺死后會由系統調度進行重啟。前述的force-stop殺死的進程,由于設置的“STOPPED”狀態是會被跳過的,因此這種情況殺死的進程不會再自動重啟。大多數ROM對此都有限制(次數、頻率)。

借助廣播喚醒

通過系統或其他App、組件發出的廣播可以喚醒應用,頑固應用可以借助廣播來完成喚醒自啟。同樣的,force-stop設置的“STOPPED”狀態也會讓廣播跳過這些App,不會喚醒這些App來傳遞廣播。但廣播帶有一個特例功能,帶有FLAG_INCLUDE_STOPPED_PACKAGES的廣播可以無視“STOPPED狀態”,仍會喚醒force-stop的App。通常系統廣播沒有這個FLAG,基本上是其他應用發出的廣播帶有。

高版本的Android已經不再觸發靜態廣播和隱式廣播,這種喚醒方式少了很多。(但有FLAG_RECEIVER_INCLUDE_BACKGROUND和FLAG_INCLUDE_STOPPED_PACKAGES規避)

借助Alarm Service定時器喚醒

Alarm是Android提供的定時器功能,定時器timeout時會喚醒App。被force-stop的應用會自動移除掉注冊的定時器,因此不會被喚醒。

借助Job Scheduling Service任務調度喚醒

與Alarm類似,定時喚醒App。但是受到電源管理策略、功耗管理策略、系統休眠狀態、WorkManager等的影響,喚醒的定時精度較低,且不同ROM可能表現一致性較差。同樣的,會跳過被force-stop的App。

借助其他App拉起喚醒

這是國內互聯網App最惡心的一種機制,一群App(或集成的SDK)互相拉起對方、互相綁定提高優先級、互相拉起喚醒。其中,喚醒方式除了常規的四大組件外,還有一些黑科技、Native的方法。其中,App發出的廣播帶上FLAG_RECEIVER_INCLUDE_BACKGROUND和FLAG_INCLUDE_STOPPED_PACKAGES完全可以規避force-stop后"STOPPED"的應用,實現喚醒。

總結

可以說,Android本身的管理機、提供的組件間通信功能,疊加App們的流氓行為,可以說后臺駐留、拉起喚醒是防不勝防的,實現較好的后臺駐留管理需要較高的投入,且對系統穩定性、App基本功能的影響較大,是高投入高難度的研究方向。其中,App互拉喚醒和保活的機制,讓force-stop機制做不到太好的效果,其"STOPPED"實現的類似的輕度凍結狀態幾乎報廢,也是各大ROM廠商在后臺管理部分大展身手的重要因素。

為了實現好的功耗、續航、性能,就需要在應用喚醒、凍結、暫停執行等方面下功夫了。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容