android:越來越難實現的進程保活

一、簡介

android中進程保活應用場景還是比較多的,音樂播放、視頻緩存等等。最近比較火的健康運動類app(說的就是我們)需要後臺獲取定位信息以及其他操作也需要進程保活。進程保活在5.0版本之前還是比較好做的,基本上都是加一個守護進程,主進程死了拉起來就可以了。但是隨着android的逐漸完善進程保活越來越不好實現了。下面是道長看到的一個博客,比較全面的分析了進程保活的手段。

二、進程被殺死的原因

Android進程被殺死的場景如下:
在這裏插入圖片描述

從上面的進程被殺死的場景分析,被殺死的原因如下:

1.Android手機的進程回收策略

Android的內存回收主要靠LowMemoryKiller 完成,Low Memorry Killer的機制主要是通過進程的oom_adj和oom_score來進行內存的處理的,關於 OOM_ADJ的說明如下:
在這裏插入圖片描述

  • 每一個進程都有一個oom_adj值,取值範圍[-17,15]。
  • 每一個進程都有一個oom_score值,它是根據oom_adj計算出一個值,分數越大越容易被殺死。
  • 內存緊張時,LMK基於oom_adj和oom_score值來決定是否要回收一個進程。
  • oom_adj值越小,越不容易被殺死。
    當 oom_adj 的值大於等於4時是比較容易被殺死的 Android進程,0-3表示不容易被殺死的Android進程,小於0的爲 非Android 進程(純 Linux 進程)尤其是-17的 native 進程不受系統管理不會被系統殺死
  • 查看oom_adj和oom_score方法:

cat proc/pid/oom_adj
cat proc/pid/oom_score

所以結合Android 的進程回收機制,若是想要不被殺死或者減少被殺死的可能性,就需要提升進程優先級,降低在內存不足被系統回收的可能性。

2.killBackgroundProcesses殺死進程

ActivityManager的killBackgroundProcesses方法,可以立即殺死與指定包相關聯的所有後臺進程,這與內核殺死那些進程回收內存是一樣的,但這些進程如果在將來某一時刻需要使用,會重新啓動。該方法需要權限Android.permission.KILL_BACKGROUND_PROCESSES。源碼解釋如下:
在這裏插入圖片描述

3.force-stop或kill殺死進程

force-stop或kill殺死進程在5.0以後做了優化,可以從源碼進行分析。5.0以下源碼如下:
在這裏插入圖片描述

通過 pid 殺死進程,因此通過主進程 fork 出來的 c 進程是不會被殺死的,但是在5.0及以上源碼發現,通過主進程 fork 出來的子進程也會被殺死了,5.0以上源碼如下:
在這裏插入圖片描述

通過源碼可以看到通過 uid 殺死進程,因此 fork 出來的子進程也同樣會被殺死。
分析了以上被殺死的場景分析,我們得出兩種技術方案:

  • Ⅰ. 提升進程優先級(降低被殺死的概率)
  • Ⅱ.進程殺死後,拉活進程

三、進程保活及分析

Ⅰ.提升進程優先級的技術手段

Android 系統將盡量長時間地保持應用進程,但是手機的內存有限,不可能無限的創建進程。最終需要清除舊進程來回收內存。爲了確定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每個進程放入“重要性層次結構”中。 必要時,系統首先清除重要性最低的進程,然後清除重要性稍低一級的進程,依此類推。
以進程的重要性劃分5級:前臺進程(Foreground process)、可見進程(Visible process)、服務進程(Service process)、後臺進程(Background process)、空進程(Empty process)

  • 前臺進程
    用戶當前操作所必需的進程。如果一個進程滿足以下任一條件,即視爲前臺進程:

1.託管用戶正在交互的Activity(已調用Activity的onResume()方法)
2.託管某個Service,後者綁定到用戶正在交互的 Activity
3.託管正在“前臺”運行的Service(服務已調用startForeground())
4.託管正執行一個生命週期回調的Service(onCreate()、onStart()或onDestroy())
5.託管正執行其onReceive()方法的BroadcastReceiver

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

  • 可見進程
    沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 如果一個進程滿足以下任一條件,即視爲可見進程:

1.託管不在前臺、但仍對用戶可見的Activity(已調用其onPause()方法)。例如,如果前臺 Activity 啓動了一個對話框,允許在其後顯示上一 Activity,則有可能會發生這種情況。
2.託管綁定到可見(或前臺)Activity 的Service。

可見進程被視爲是極其重要的進程,除非爲了維持所有前臺進程同時運行而必須終止,否則系統不會終止這些進程。

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

  • 後臺進程
    包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的onStop()方法)。這些進程對用戶體驗沒有直接影響,系統可能隨時終止它們,以回收內存供前臺進程、可見進程或服務進程使用。 通常會有很多後臺進程在運行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。如果某個 Activity 正確實現了生命週期方法,並保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,因爲當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態。 有關保存和恢復狀態的信息,請參閱Activity文檔。

  • 空進程
    不含任何活動應用組件的進程。保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啓動時間。 爲使總體系統資源在進程緩存和底層內核緩存之間保持平衡,系統往往會終止這些進程。

根據進程中當前活動組件的重要程度,Android 會將進程評定爲它可能達到的最高級別。例如,如果某進程託管着服務和可見 Activity,則會將此進程評定爲可見進程,而不是服務進程。
前臺進程的重要性最高,依次遞減,空進程的重要性最低,下面分別來闡述每種級別的進程,進程的回收策略如上,進程的介紹可詳見:
http://www.jianshu.com/p/8a95f1f82ede?utm_source=desktop&utm_medium=timeline

1.像素懸浮層

監控手機鎖屏解鎖事件,在屏幕鎖屏時啓動1個像素的 Activity,在用戶解鎖時將 Activity 銷燬掉。注意該 Activity 需設計成用戶無感知。通過該方案,可以使進程的優先級在屏幕鎖屏時間由4提升爲最高優先級1,主要解決第三方應用及系統管理工具在檢測到鎖屏事件後一段時間(一般爲5分鐘以內)內會殺死後臺進程,已達到省電的目的問題
實現方案:
首先定義 Activity,並設置 Activity 的大小爲1像素:
在這裏插入圖片描述
其次,從 AndroidManifest 中通過如下屬性,排除 Activity 在 RecentTask 中的顯示:
在這裏插入圖片描述
最後,控制 Activity 爲透明:
在這裏插入圖片描述
Activity 啓動與銷燬時機的控制:
在這裏插入圖片描述

2.將Service設置爲前臺服務

Android 中 Service 的優先級爲4,通過 setForeground 接口可以將後臺 Service 設置爲前臺 Service,使進程的優先級由4提升爲2,從而使進程的優先級僅僅低於用戶當前正在交互的進程,與可見進程優先級一致,使進程被殺死的概率大大降低。
從 Android2.3 開始調用 setForeground 將後臺 Service 設置爲前臺 Service 時,必須在系統的通知欄發送一條通知,也就是前臺 Service 與一條可見的通知時綁定在一起的。對於不需要常駐通知欄的應用來說,該方案雖好,但卻是用戶感知的,無法直接使用。
通過實現一個內部 Service,在 KeepLiveService 和其內部 Service 中同時發送具有相同 ID 的 Notification,然後將內部 Service 結束掉。隨着內部 Service 的結束,Notification 將會消失,但系統優先級依然保持爲2。

具體實現方案:
在這裏插入圖片描述

在這裏插入圖片描述

3.定製化

向手機廠商申請定製,把app的進程加入系統白名單。當然這個方法儘管可以達到最優效果,但是並不是每一個app能夠辦到。

Ⅱ.進程死後,拉活進程

1.在service的onstart方法裏返回 START_STICK

主要的幾個返回值:

1.START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保證服務被kill後一定能重啓。
2.START_STICKY:系統就會重新創建這個服務並且調用onStartCommand()方法,但是它不會重新傳遞最後的Intent對象,這適用於不執行命令的媒體播放器(或類似的服務),它只是無限期的運行着並等待工作的到來。
3.START_NOT_STICKY:直到接受到新的Intent對象,纔會被重新創建。這是最安全的,用來避免在不需要的時候運行你的服務。
4.START_REDELIVER_INTENT:系統就會重新創建了這個服務,並且用最後的Intent對象調。等待中的Intent對象會依次被髮送。這適用於如下載文件。

具體實現方案:
將 Service 設置爲 START_STICKY,利用系統機制在 Service 掛掉後自動拉活:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        return START_STICKY;//異常結束自啓動
    }

如下兩種情況無法拉活:

  1. Service 第一次被異常殺死後會在5秒內重啓,第二次被殺死會在10秒內重啓,第三次會在20秒內重啓,一旦在短時間內 Service 被殺死達到5次,則系統不再拉起。

  2. 進程被取得 Root 權限的管理工具或系統工具通過 forestop 停止掉,無法重啓。因此force close和一些清理軟件很容易就清理掉!

2.添加Manifest文件屬性值爲android:persistent=“true”

app.persistent = true不僅僅標誌着此apk不能輕易的被kill掉,亦或在被kill掉後能夠自動restart,並且還涉及到了進程的優先級。將被設置爲CORE_SERVER_ADJ,此值爲-12,而核心進程init的值爲-16。當前正在前臺運行的進程的值爲0。如果應用能設置這個屬性,那就真的可以做到保活,因爲他真的可以殺不死,像系統的keyguard進程,media進程,且這些進程的adj都是負數,代表了前臺activity黑屏了他們也不會死。但是這個屬性需要系統shareuid,然後編譯不過,因爲需要系統簽名!
因此,要弄這個屬性需獲取兩個關鍵的信息:

1.在apk的AndroidManifest.xml文件中設置android:persistent=true
2.此apk需要放入到system/app目錄下,成爲一個systemapp

最主要的是讓程序成爲系統程序,這個可以做到嗎?如果你技術過硬是可以嘗試下的!首先弄個自動root的apk,加殼放到程序中,然後程序運行的時候,自動運行自動root的apk,獲取root權限,然後在native層中,使用命令的方式把程序移到system/app目錄下,成爲系統程序!

3.覆寫Service的onDestroy方法

在設置裏面的正在運行,注意是正在運行裏面,點擊關閉,會走onDestroy回調方法,你在這裏可以把自己啓動起來。注意是正常關閉的時候是會自己啓動起來,可是使用第三方的清理軟件360,root過的360,force close這些來搞,壓根不會走到onDestory的方法

4.監聽一堆系統靜態廣播

在發生特定系統事件時,系統會發出響應的廣播,通過在 AndroidManifest 中“靜態”註冊對應的廣播監聽器,即可在發生響應事件時拉活。常用的用於拉活的廣播事件包括:
在這裏插入圖片描述

但是有如下問題:

  1. 廣播接收器被管理軟件、系統軟件通過“自啓管理”等功能禁用的場景無法接收到廣播,從而無法自啓。

  2. 系統廣播事件不可控,只能保證發生事件時拉活進程,但無法保證進程掛掉後立即拉活。

5.監聽第三方應用的靜態廣播

與接收系統廣播類似,不同的是該方案爲接收第三方應用廣播。通過反編譯第三方應用,如:手機QQ、微信、支付寶、UC瀏覽器等找出它們外發的廣播,在應用中進行監聽,這樣當這些應用發出廣播時,就會將我們的應用拉活。
有效程度除與系統廣播一樣的因素外, 第三方應用的廣播屬於應用私有,當前版本中有效的廣播,在後續版本隨時就可能被移除或被改爲不外發。

6.AlarmManager喚醒

主要是實現也一個監聽開機的廣播,和一個週期性的鬧鐘,不過比較致命的是耗電量是很高的

7.賬戶同步,定時喚醒

android系統裏有一個賬戶系統,系統定期喚醒賬號更新服務,同步的事件間隔是有限制的,最短1分鐘,利用同步機制進行進程的拉活。難點:需要手動設置賬戶,你如何騙你的用戶給你手動設置賬戶完了之後不卸載你,必須聯網
添加賬號和設置同步週期的代碼如下:
在這裏插入圖片描述

該方案需要在 AndroidManifest 中定義賬號授權與同步服務。
在這裏插入圖片描述

理論上使用與所有 Android 系統,也能解決 force-stop 的拉活,但是前提是需要聯網狀態下

注:最新 Android 版本(Android N)中系統好像對賬戶同步這裏做了變動,該方法不再有效。

8.雙服務守護

這個是android裏面一個特性,跨進程bind一個service之後,如果被bind的service掛掉,bind他的service會把他拉起來!可以考慮使用遠程服務來實現,可是還是不能保活進程,可以被殺掉!

9.多APP間互相拉起

比較常見的就是家族 app 之間互相調起,你監聽到我死了,我把你拉起,之間互相拉活對方

10.Native進程拉起

原理就是通過 JNI fork出一個 c 進程,c 進程監控主進程是否存活,主要通過管道和文件監控的方式實現監控,發現主進程死後,通過調起一個 service 將主進程拉活

具體的實現方案:
在這裏插入圖片描述

在這裏插入圖片描述
此方案存在兩個問題:

  1. 如果直接使用 JNI fork 出一個子進程,這樣會存在一個內存較大問題,也就是主版佔用多大內存,native 進程也佔用多大內存,因此需要去寫一個獨立的 c 進程來降低內存大小

  2. 上面講到5.0以上的系統會將根據 uid 殺進程,因此 native 進程也會被殺死,因此在5.0以上此方案失效的,

11.雙進程守護

顧名思義開啓兩個 native 進程進行保活,當一個 native 進程被殺死後,另一個 native 進行拉起,具體實現方案可參考:http://blog.csdn.net/marswin89/article/details/50916631

12.JobSchedule機制拉活

Android5.0 以後系統對 Native 進程等加強了管理,Native 拉活方式失效。系統在 Android5.0 以上版本提供了 JobScheduler 接口,系統會定時調用該進程以使應用進行一些邏輯操作。

具體實現方案:
在這裏插入圖片描述

通過創建一個系統任務JobInfo,添加到 JobSchedule 中,通過系統調度任務來實現拉活,force-stop 也能實現拉活。但是存在很多兼容性問題,目前在華爲6.0以上不生效,以及在7.0系統上不能實現,根據官方文檔說是在 Dos 安全模式下,系統調度也會被殺死。

原文鏈接:https://www.jianshu.com/p/8330a73771ca

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章