天天看點

【騰訊Bugly幹貨分享】Android程序保活招式大全

目前市面上的應用,貌似除了微信和手Q都會比較擔心被使用者或者系統(廠商)殺死問題。本文對 Android 程序拉活進行一個總結。

本文來自于騰訊bugly開發者社群,非經作者同意,請勿轉載,原文位址:http://dev.qq.com/topic/57ac4a0ea374c75371c08ce8

作者:騰訊——張興華

Android 程序拉活包括兩個層面:

A. 提供程序優先級,降低程序被殺死的機率

B. 在程序被殺死後,進行拉活

本文下面就從這兩個方面做一下總結。

1. 程序的優先級

Android 系統将盡量長時間地保持應用程序,但為了建立程序或運作更重要的程序,最終需要清除舊程序來回收記憶體。 為了确定保留或終止哪些程序,系統會根據程序中正在運作的元件以及這些元件的狀态,将每個程序放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的程序,然後是清除重要性稍低一級的程序,依此類推,以回收系統資源。

程序的重要性,劃分5級:

  1. 前台程序(Foreground process)
  2. 可見程序(Visible process)
  3. 服務程序(Service process)
  4. 背景程序(Background process)
  5. 空程序(Empty process)

前台程序的重要性最高,依次遞減,空程序的重要性最低,下面分别來闡述每種級别的程序

1.1. 前台程序 —— Foreground process

使用者目前操作所必需的程序。通常在任意給定時間前台程序都為數不多。隻有在記憶體不足以支援它們同時繼續運作這一萬不得已的情況下,系統才會終止它們。

A. 擁有使用者正在互動的 Activity(已調用

onResume()

B. 擁有某個 Service,後者綁定到使用者正在互動的 Activity

C. 擁有正在“前台”運作的 Service(服務已調用

startForeground()

D. 擁有正執行一個生命周期回調的 Service(

onCreate()

onStart()

onDestroy()

E. 擁有正執行其

onReceive()

方法的 BroadcastReceiver

1.2. 可見程序 —— Visible process

沒有任何前台元件、但仍會影響使用者在螢幕上所見内容的程序。可見程序被視為是極其重要的程序,除非為了維持所有前台程序同時運作而必須終止,否則系統不會終止這些程序。

A. 擁有不在前台、但仍對使用者可見的 Activity(已調用

onPause()

)。

B. 擁有綁定到可見(或前台)Activity 的 Service

1.3. 服務程序 —— Service process

盡管服務程序與使用者所見内容沒有直接關聯,但是它們通常在執行一些使用者關心的操作(例如,在背景播放音樂或從網絡下載下傳資料)。是以,除非記憶體不足以維持所有前台程序和可見程序同時運作,否則系統會讓服務程序保持運作狀态。

A. 正在運作

startService()

方法啟動的服務,且不屬于上述兩個更高類别程序的程序。

1.4. 背景程序 —— Background process

背景程序對使用者體驗沒有直接影響,系統可能随時終止它們,以回收記憶體供前台程序、可見程序或服務程序使用。 通常會有很多背景程序在運作,是以它們會儲存在 LRU 清單中,以確定包含使用者最近檢視的

Activity

的程序最後一個被終止。如果某個 Activity 正确實作了生命周期方法,并儲存了其目前狀态,則終止其程序不會對使用者體驗産生明顯影響,因為當使用者導航回該 Activity 時,Activity 會恢複其所有可見狀态。

A. 對使用者不可見的 Activity 的程序(已調用

Activity的onStop()

方法)

1.5. 空程序 —— Empty process

保留這種程序的的唯一目的是用作緩存,以縮短下次在其中運作元件所需的啟動時間。 為使總體系統資源在程序緩存和底層核心緩存之間保持平衡,系統往往會終止這些程序。

A. 不含任何活動應用元件的程序

詳情參見:http://developer.android.com/intl/zh-cn/guide/components/processes-and-threads.html

2. Android 程序回收政策

Android 中對于記憶體的回收,主要依靠 Lowmemorykiller 來完成,是一種根據 OOM_ADJ 門檻值級别觸發相應力度的記憶體回收的機制。

關于 OOM_ADJ 的說明如下:

其中紅色部分代表比較容易被殺死的 Android 程序(OOM_ADJ>=4),綠色部分表示不容易被殺死的 Android 程序,其他表示非 Android 程序(純 Linux 程序)。在 Lowmemorykiller 回收記憶體時會根據程序的級别優先殺死 OOM_ADJ 比較大的程序,對于優先級相同的程序則進一步受到程序所占記憶體和程序存活時間的影響。

Android 手機中程序被殺死可能有如下情況:

綜上,可以得出減少程序被殺死機率無非就是想辦法提高程序優先級,減少程序在記憶體不足等情況下被殺死的機率。

3. 提升程序優先級的方案

3.1. 利用 Activity 提升權限

3.1.1. 方案設計思想

監控手機鎖屏解鎖事件,在螢幕鎖屏時啟動1個像素的 Activity,在使用者解鎖時将 Activity 銷毀掉。注意該 Activity 需設計成使用者無感覺。

通過該方案,可以使程序的優先級在螢幕鎖屏時間由4提升為最高優先級1。

3.1.2. 方案适用範圍

适用場景: 本方案主要解決第三方應用及系統管理工具在檢測到鎖屏事件後一段時間(一般為5分鐘以内)内會殺死背景程序,已達到省電的目的問題。

适用版本: 适用于所有的 Android 版本。

3.1.3. 方案具體實作

首先定義 Activity,并設定 Activity 的大小為1像素:

其次,從 AndroidManifest 中通過如下屬性,排除 Activity 在 RecentTask 中的顯示:

最後,控制 Activity 為透明:

Activity 啟動與銷毀時機的控制:

3.2. 利用 Notification 提升權限

3.2.1. 方案設計思想

Android 中 Service 的優先級為4,通過 setForeground 接口可以将背景 Service 設定為前台 Service,使程序的優先級由4提升為2,進而使程序的優先級僅僅低于使用者目前正在互動的程序,與可見程序優先級一緻,使程序被殺死的機率大大降低。

3.2.2. 方案實作挑戰

從 Android2.3 開始調用 setForeground 将背景 Service 設定為前台 Service 時,必須在系統的通知欄發送一條通知,也就是前台 Service 與一條可見的通知時綁定在一起的。

對于不需要常駐通知欄的應用來說,該方案雖好,但卻是使用者感覺的,無法直接使用。

3.2.3. 方案挑戰應對措施

通過實作一個内部 Service,在 LiveService 和其内部 Service 中同時發送具有相同 ID 的 Notification,然後将内部 Service 結束掉。随着内部 Service 的結束,Notification 将會消失,但系統優先級依然保持為2。

3.2.4. 方案适用範圍

适用于目前已知所有版本。

3.2.5. 方案具體實作

4. 程序死後拉活的方案

4.1. 利用系統廣播拉活

4.1.1. 方案設計思想

在發生特定系統事件時,系統會發出響應的廣播,通過在 AndroidManifest 中“靜态”注冊對應的廣播監聽器,即可在發生響應事件時拉活。

常用的用于拉活的廣播事件包括:

4.1.2. 方案适用範圍

适用于全部 Android 平台。但存在如下幾個缺點:

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

2) 系統廣播事件不可控,隻能保證發生事件時拉活程序,但無法保證程序挂掉後立即拉活。

是以,該方案主要作為備用手段。

4.2. 利用第三方應用廣播拉活

4.2.1. 方案設計思想

該方案總的設計思想與接收系統廣播類似,不同的是該方案為接收第三方 Top 應用廣播。

通過反編譯第三方 Top 應用,如:手機QQ、微信、支付寶、UC浏覽器等,以及友盟、信鴿、個推等 SDK,找出它們外發的廣播,在應用中進行監聽,這樣當這些應用發出廣播時,就會将我們的應用拉活。

4.2.2. 方案适用範圍

該方案的有效程度除與系統廣播一樣的因素外,主要受如下因素限制:

1) 反編譯分析過的第三方應用的多少

2) 第三方應用的廣播屬于應用私有,目前版本中有效的廣播,在後續版本随時就可能被移除或被改為不外發。

這些因素都影響了拉活的效果。

4.3. 利用系統Service機制拉活

4.3.1. 方案設計思想

将 Service 設定為 START_STICKY,利用系統機制在 Service 挂掉後自動拉活:

4.3.2. 方案适用範圍

如下兩種情況無法拉活:

  1. Service 第一次被異常殺死後會在5秒内重新開機,第二次被殺死會在10秒内重新開機,第三次會在20秒内重新開機,一旦在短時間内 Service 被殺死達到5次,則系統不再拉起。
  2. 程序被取得 Root 權限的管理工具或系統工具通過 forestop 停止掉,無法重新開機。

4.4. 利用Native程序拉活

4.4.1. 方案設計思想

主要思想:利用 Linux 中的 fork 機制建立 Native 程序,在 Native 程序中監控主程序的存活,當主程序挂掉後,在 Native 程序中立即對主程序進行拉活。

主要原理:在 Android 中所有程序和系統元件的生命周期受 ActivityManagerService 的統一管理。而且,通過 Linux 的 fork 機制建立的程序為純 Linux 程序,其生命周期不受 Android 的管理。

4.4.2. 方案實作挑戰

挑戰一:在 Native 程序中如何感覺主程序死亡。

要在 Native 程序中感覺主程序是否存活有兩種實作方式:

  1. 在 Native 程序中通過死循環或定時器,輪訓判斷主程序是否存活,檔主程序不存活時進行拉活。該方案的很大缺點是不停的輪詢執行判斷邏輯,非常耗電。
  2. 在主程序中建立一個監控檔案,并且在主程序中持有檔案鎖。在拉活程序啟動後申請檔案鎖将會被堵塞,一旦可以成功擷取到鎖,說明主程序挂掉,即可進行拉活。由于 Android 中的應用都運作于虛拟機之上,Java 層的檔案鎖與 Linux 層的檔案鎖是不同的,要實作該功能需要封裝 Linux 層的檔案鎖供上層調用。

    封裝 Linux 檔案鎖的代碼如下:

    Native 層中堵塞申請檔案鎖的部分代碼:

    挑戰二:在 Native 程序中如何拉活主程序。

    通過 Native 程序拉活主程序的部分代碼如下,即通過 am 指令進行拉活。通過指定“—include-stopped-packages”參數來拉活主程序處于 forestop 狀态的情況。

    挑戰三:如何保證 Native 程序的唯一。

    從可擴充性和程序唯一等多方面考慮,将 Native 程序設計層 C/S 結構模式,主程序與 Native 程序通過 Localsocket 進行通信。在Native程序中利用 Localsocket 保證 Native 程序的唯一性,不至于出現建立多個 Native 程序以及 Native 程序變成僵屍程序等問題。

    4.4.3. 方案适用範圍

    該方案主要适用于 Android5.0 以下版本手機。

    該方案不受 forcestop 影響,被強制停止的應用依然可以被拉活,在 Android5.0 以下版本拉活效果非常好。

    對于 Android5.0 以上手機,系統雖然會将native程序内的所有程序都殺死,這裡其實就是系統“依次”殺死程序時間與拉活邏輯執行時間賽跑的問題,如果可以跑的比系統邏輯快,依然可以有效拉起。記得網上有人做過實驗,該結論是成立的,在某些 Android 5.0 以上機型有效。

    4.5. 利用 JobScheduler 機制拉活

    4.5.1. 方案設計思想

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

    在本項目中,我對 JobScheduler 進行了進一步封裝,相容 Android5.0 以下版本。封裝後 JobScheduler 接口的使用如下:

    4.5.2. 方案适用範圍

    該方案主要适用于 Android5.0 以上版本手機。

    該方案在 Android5.0 以上版本中不受 forcestop 影響,被強制停止的應用依然可以被拉活,在 Android5.0 以上版本拉活效果非常好。

    僅在小米手機可能會出現有時無法拉活的問題。

    4.6. 利用賬号同步機制拉活

    4.6.1. 方案設計思想

    Android 系統的賬号同步機制會定期同步賬号進行,該方案目的在于利用同步機制進行程序的拉活。添加賬号和設定同步周期的代碼如下:

    該方案需要在 AndroidManifest 中定義賬号授權與同步服務。

    4.6.2. 方案适用範圍

    該方案适用于所有的 Android 版本,包括被 forestop 掉的程序也可以進行拉活。

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

    5. 其他有效拉活方案

    經研究發現還有其他一些系統拉活措施可以使用,但在使用時需要使用者授權,使用者感覺比較強烈。

    這些方案包括:

  3. 利用系統通知管理權限進行拉活
  4. 利用輔助功能拉活,将應用加入廠商或管理軟體白名單。

    這些方案需要結合具體産品特性來搞。

    上面所有解釋這些方案都是考慮的無 Root 的情況。

    其他還有一些技術之外的措施,比如說應用内 Push 通道的選擇:

  5. 國外版應用:接入 Google 的 GCM。
  6. 國内版應用:根據終端不同,在小米手機(包括 MIUI)接入小米推送、華為手機接入華為推送;其他手機可以考慮接入騰訊信鴿或極光推送與小米推送做 A/B Test。

更多精彩内容歡迎關注bugly的微信公衆賬号:

騰訊 Bugly是一款專為移動開發者打造的品質監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智能合并功能幫助開發同學把每天上報的數千條 Crash 根據根因合并分類,每日日報會列出影響使用者數最多的崩潰,精準定位功能幫助開發同學定位到出問題的代碼行,實時上報可以在釋出後快速的了解應用的品質情況,适配最新的 iOS, Android 官方作業系統,鵝廠的工程師都在使用,快來加入我們吧!