現(xiàn)象
android o版本(8.0)及以上版本,當(dāng)應(yīng)用處于后臺(tái)時(shí)執(zhí)行startService時(shí),會(huì)拋出如下異常:
Caused by: java.lang.IllegalStateException: Not allowed to start service ... app is in background uid UidRecord ...
初步理解為由于app處于后臺(tái)時(shí)startServic不被允許
原因分析
從android O版本開始,google為了控制資源使用增加了兩項(xiàng)后臺(tái)限制:
后臺(tái)服務(wù)
廣播
其中對(duì)于后臺(tái)服務(wù)的限制,指的是如果應(yīng)用處于后臺(tái),則不允許直接使用startService。
那什么是后臺(tái)應(yīng)用?后臺(tái)的對(duì)立面是前臺(tái),google對(duì)于前臺(tái)的定義如下:
具有可見的activity(不管該activity已啟動(dòng)還是已暫停)
具有前臺(tái)服務(wù)
有關(guān)聯(lián)的前臺(tái)應(yīng)用(如壁紙,通知偵聽器等)
具體可參考官方鏈接:
https://developer.android.com/about/versions/oreo/background
解決方案
既然我們使用了后臺(tái)服務(wù),必然是一些無(wú)需用戶感知的場(chǎng)景,故考慮替換為前臺(tái)服務(wù)的可能性不大;
從google對(duì)前臺(tái)應(yīng)用的解釋入手,可以通過(guò)制造前臺(tái)場(chǎng)景的方式使自己的應(yīng)用處于前臺(tái);
官方還提及了JobScheduler替代后臺(tái)服務(wù)的方案,可以根據(jù)的業(yè)務(wù)場(chǎng)景選擇是否使用;
startService的方式也不需要完全放棄。為了適配8.0手機(jī),需要先判斷應(yīng)用是否在前臺(tái)再?zèng)Q定是否使用startService;
判斷應(yīng)用是否在前臺(tái)的方式很多,主要原理有兩種:
通過(guò)AM判斷應(yīng)用前臺(tái)activity個(gè)數(shù)
通過(guò)actvitiy生命周期回調(diào)計(jì)數(shù)(Activity回調(diào)、ActivityLifecycleCallbacks)判斷是否有前臺(tái)界面
實(shí)現(xiàn)的方式很多,自行g(shù)oogle
設(shè)置targetSdk < 26也可以實(shí)現(xiàn)該新特性規(guī)避。
源碼分析
startService大致流程如下:
ContextImpl#startServce ->
ContextImpl#startServceCommon ->
AMS#startService ->
ActiveServices#startServiceLocked ->
AMS#checkAllowBackgroundLocked
8.0.0
ContextImpl.java#startServiceCommon
紅框中為日志信息的來(lái)源
ActivityServices.java#startServiceLocked
另一處Log信息的來(lái)源:app is in background
r.startRequested第一次初始化,默認(rèn)為false;fgRequired為傳入的值false,故可以進(jìn)入后續(xù)邏輯;
通過(guò)AMS#getAppStartModeLocked獲取的allowed為決定值,意思是是否允許后臺(tái)運(yùn)行,返回值有多種類型(從代碼提交中可以看出原先是boolean,現(xiàn)在是int)。
google 在一年前(2016~2017)針對(duì)后臺(tái)應(yīng)用的判斷以及后臺(tái)執(zhí)行限制做了大量的提交,從記錄中可以看到:
之前提到通過(guò)設(shè)置targetSdk<26的方式就可以,但是這僅僅是從8.0特性的代碼層面分析得知,這種方式并不能阻止7.x系統(tǒng)中對(duì)后臺(tái)服務(wù)的限制。
ActiveServices.java # startServiceLocked
7.1.1 AMS 是否允許后臺(tái)服務(wù)啟動(dòng)所關(guān)心的主要還是 當(dāng)前進(jìn)程的優(yōu)先級(jí) 和 是否有后臺(tái)運(yùn)行權(quán)限
8.0 AMS 中間的流程較7.x稍微復(fù)雜一些,大致流程如下:
AMS # getAppStartModeLocked ->
AMS # appServicesRestrictedInBackgroundLocked ->
AMS # appRestrictedInBackgroundLocked
這里相當(dāng)于是后臺(tái)服務(wù)特權(quán)的檢查,只要滿足三者之一:
有persistent權(quán)限
后臺(tái)運(yùn)行白名單
電源優(yōu)化白名單
就直接可以使用后臺(tái)服務(wù),前兩個(gè)均為系統(tǒng)應(yīng)用才能設(shè)置;
均不滿足則繼續(xù)走后面的判斷流程,重點(diǎn)來(lái)了,8.0中優(yōu)先判斷targetSdk是否在O版本及以上,是則直接返回不允許,否才會(huì)判斷是否有后臺(tái)運(yùn)行權(quán)限!
所以,如果需要有后臺(tái)運(yùn)行的邏輯,8.0以下版本優(yōu)先開啟后臺(tái)運(yùn)行權(quán)限,8.0及以上版本優(yōu)先開啟電源優(yōu)化白名單。
隨著Android版本的更新,早先年使用的?;詈诳萍贾饾u都失效了,現(xiàn)在需要著重在產(chǎn)品層面設(shè)計(jì),引導(dǎo)用戶開啟相關(guān)權(quán)限才是迫在眉睫的需求。
總之,Google對(duì)后臺(tái)的限制越來(lái)越嚴(yán)格,不僅限制了各種拉活行為,也限制了搶占后臺(tái)資源的行為,各大app需要提前做好高版本適配。