前言
當(dāng)我們的App正在與用戶頻繁交互時,需要處理某個耗時任務(wù),而任務(wù)的結(jié)果需要立即反饋。這時因為主線程用于處理UI和用戶交互邏輯,如果有太多的耗時的邏輯在主線程中執(zhí)行,就會阻塞主線程,引發(fā)ANR異常,導(dǎo)致APK卡頓甚至崩潰。因此需要一個后臺線程來處理耗時任務(wù)。
當(dāng)我們的App并未與用戶頻繁交互,但是,App本身需要周期性的從服務(wù)器同步數(shù)據(jù)或者獲取數(shù)據(jù)(常見的心跳連接、收發(fā)消息長連接)。這時需要一個后臺進(jìn)程來處理,因為僅僅是后臺線程,不能保證長久存活,也不能保證任務(wù)能執(zhí)行完畢。
關(guān)于進(jìn)程?;畹奈恼掠泻芏嗔?,推薦幾篇不錯的文章:
騰訊——張興華 原文鏈接找不到了,這是別人轉(zhuǎn)發(fā)
關(guān)于 Android 進(jìn)程?;睿闼枰赖囊磺?/a>
但是,官方說明自己看:
Note: You should only use a foreground service for tasks the user expects the system to execute immediately or without interruption. Such cases include uploading a photo to social media, or playing music even while the music-player app is not in the foreground. You should not start a foreground service simply to prevent the system from determining that your app is idle.
本文的目的在于參考官方文檔,解決后臺進(jìn)程處理的相關(guān)問題,未必要保活。
一、低電耗(Doze)模式
Doze會通過推遲應(yīng)用程序的后臺CPU和網(wǎng)絡(luò)活動來減少電池消耗。
系統(tǒng)會定期退出Doze一段時間,讓應(yīng)用程序完成延期活動。在此維護窗口期間,系統(tǒng)將運行所有掛起的同步,作業(yè)和警報,并允許應(yīng)用程序訪問網(wǎng)絡(luò)。
在每個維護窗口結(jié)束時,系統(tǒng)再次進(jìn)入Doze,暫停網(wǎng)絡(luò)訪問并推遲作業(yè),同步和警報。隨著時間的推移,系統(tǒng)會越來越少地安排維護窗口,有助于在設(shè)備未連接到充電器時長時間不活動時減少電池消耗。
一旦用戶通過移動設(shè)備,打開屏幕或連接充電器喚醒設(shè)備,系統(tǒng)退出Doze并且所有應(yīng)用程序恢復(fù)正?;顒?。
功能限制
在低電耗模式下,您的應(yīng)用會受到以下限制:
- 暫停訪問網(wǎng)絡(luò)。
- 系統(tǒng)忽略喚醒鎖定。
- 標(biāo)準(zhǔn) AlarmManager 鬧鐘(包括
setExact()和setWindow())推遲到下一個維護期。 - 如果您需要設(shè)置在設(shè)備處于低電耗模式時觸發(fā)的鬧鐘,請使用
setAndAllowWhileIdle()或setExactAndAllowWhileIdle(),但9分鐘內(nèi)只能觸發(fā)一次鬧鐘。使用setAlarmClock()設(shè)置的鬧鐘將繼續(xù)正常觸發(fā),系統(tǒng)會在這些鬧鐘觸發(fā)之前不久退出低電耗模式。 - 系統(tǒng)不執(zhí)行 WLAN 掃描。
- 系統(tǒng)不允許運行同步適配器。
- 系統(tǒng)不允許運行 JobScheduler。
adb指令強制進(jìn)入Doze模式
$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step 切換到下一個狀態(tài),idle和active狀態(tài)之間切換
$ adb shell dumpsys deviceidle -h 查看幫助
$ adb shell dumpsys deviceidle force-idle [light|deep] 強制進(jìn)入idle狀態(tài)
$ adb shell dumpsys deviceidle force-inactive 強制進(jìn)入inactive狀態(tài)
$ adb shell dumpsys deviceidle unforce 強制解除idle、inactive狀態(tài),進(jìn)入active狀態(tài)
$ adb shell dumpsys deviceidle get [light|deep|force|screen|charging|network] 獲取相應(yīng)的當(dāng)前狀態(tài)
$ adb shell dumpsys battery reset 復(fù)位默認(rèn)值,恢復(fù)設(shè)備激活狀態(tài)。
二、應(yīng)用待機模式(App Standby)
App Standby推遲用戶最近未與之交互的應(yīng)用的后臺網(wǎng)絡(luò)活動。
adb指令強制進(jìn)入standby模式
$ adb shell dumpsys battery unplug
$ adb shell am set-inactive <packageName> true 進(jìn)入standby 模式
$ adb shell am set-inactive <packageName> false 退出standby模式,恢復(fù)到激活狀態(tài)
$ adb shell am get-inactive <packageName> 獲取當(dāng)前狀態(tài)
$ adb shell dumpsys battery reset 復(fù)位默認(rèn)值。
三、Android不同版本對省電的優(yōu)化
為了最大化電池并強制執(zhí)行良好的應(yīng)用行為,當(dāng)用戶看不到應(yīng)用(或前臺服務(wù)通知)時,Android會限制后臺工作。
模擬App在后臺運行的環(huán)境:
adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
Android 6.0(API級別23)
引入了Doze模式和App Standby管理。當(dāng)屏幕關(guān)閉且設(shè)備靜止時,Doze模式會限制應(yīng)用程序行為。應(yīng)用程序待機將未使用的應(yīng)用程序置于特殊狀態(tài),限制其網(wǎng)絡(luò)訪問,作業(yè)和同步。

Android 7.0(API級別24)
限制了隱式廣播并引入了 Doze-on-the-Go(縮短了進(jìn)入Doze模式的時間,相關(guān)限制分時分優(yōu)先級進(jìn)行)。
三個被限制的隱式廣播:
CONNECTIVITY_ACTION
ACTION_NEW_PICTURE
ACTION_NEW_VIDEO

Android 8.0(API級別26)
進(jìn)一步限制了后臺行為,例如在后臺獲取位置和釋放緩存的喚醒鎖。
限制后臺進(jìn)程:應(yīng)用在前臺時,可以隨意創(chuàng)建前臺和后臺服務(wù);應(yīng)用剛剛進(jìn)入后臺時擁有一個幾分鐘的窗口,此時也可以隨意創(chuàng)建前臺和后臺服務(wù);進(jìn)入idle狀態(tài)時,系統(tǒng)會終止應(yīng)用的后臺服務(wù)。
多數(shù)情況下JobScheduler(官方Demo)可以取代后臺服務(wù),JobIntentService取代IntentService。
后臺應(yīng)用不能通過startService()啟動服務(wù)了,只能通過startForegroundService()方法啟動服務(wù),然后應(yīng)用有5秒鐘時間調(diào)用startForground()方法將服務(wù)置為前臺服務(wù),如果規(guī)定時間內(nèi)沒有調(diào)用該方法,app就會拋出 ANR 異常。
不能注冊隱式廣播接收者了(所謂隱式廣播就是沒有指定目標(biāo)應(yīng)用的廣播,哪個應(yīng)用都可以接收這樣的廣播)。
依然能注冊顯式廣播。
不再有需要簽名權(quán)限的廣播,因為以后廣播只會發(fā)送給簽名相同的app了。
Android 9(API級別28)
引入了 App Standby Buckets,其中應(yīng)用程序?qū)Y源的請求根據(jù)應(yīng)用程序使用模式進(jìn)行動態(tài)優(yōu)先級排序,共有5個級別。
Active: 正在被使用的或最近常被使用的App
Working Set: 周期性頻繁被使用的App
Frequent: 常被使用,但不是每天都用的App
Rare: 很少被使用的App
Never: 安裝后未被使用過的App
四、針對省電優(yōu)化的解決方案
對于解決如何確保應(yīng)用能優(yōu)雅地處理后臺任務(wù),官方提供了最佳實踐方案:

WorkManager不熟悉,別的都很好理解,對于WorkManager 說明一下:
對于可延遲的、異步的、即使您的設(shè)備或應(yīng)用程序重新啟動也要運行的工作,請使用 WorkManager。WorkManager 可以在滿足工作條件(如網(wǎng)絡(luò)可用性和功率)時優(yōu)雅地運行可延遲的后臺工作。
- WorkManager可以向后兼容到
API 14+。-
API 23+及以上版本實際上使用的是 JobScheduler -
API 14-22使用的是AlarmManager& BroadcastReceiver
-
- 可以添加任務(wù)執(zhí)行的約束條件,如網(wǎng)絡(luò)狀態(tài)、充電狀態(tài)
- 可以處理一次性的異步任務(wù)、周期性的任務(wù)
- 可以監(jiān)視,管理已經(jīng)安排好的任務(wù)
- 任務(wù)執(zhí)行是有先后順序的,先觸發(fā)的先執(zhí)行
- 能確保任務(wù)得到執(zhí)行,即使應(yīng)用重啟或設(shè)備重啟
- 是遵從省電特性的,符合省電優(yōu)化要求
使用WorkManager的典型場景:
- 發(fā)送logs、analytics 到后臺服務(wù)器
- 周期性的從服務(wù)器獲取或同步數(shù)據(jù)