iOS 定時(shí)器進(jìn)入后臺繼續(xù)運(yùn)行問題探索

一. 問題背景

最近項(xiàng)目中有個(gè)定時(shí)器計(jì)時(shí)實(shí)時(shí)更新等車的時(shí)長,因?yàn)轫?xiàng)目里面進(jìn)入后臺是有執(zhí)行一些任務(wù)的操作,因此如果進(jìn)入后臺時(shí)間不長,是定時(shí)器是不會暫停的,但如果進(jìn)入后臺時(shí)間,超過20s以上,定時(shí)器就暫停,回到前臺重新開始倒計(jì)時(shí),這時(shí)候等車的時(shí)長會出現(xiàn)不準(zhǔn)的情況。

二. 問題原因

經(jīng)驗(yàn)證NSTimer,CADisplayLink,dispatch_source_t,三個(gè)定時(shí)器,在進(jìn)入到后臺的時(shí)候,都會暫停,等到返回前臺的時(shí)候,才會繼續(xù)回調(diào)。

timer.gif

看了一些博客說加上后臺任務(wù)執(zhí)行這句話可以保證App進(jìn)入后臺,定時(shí)器不會暫停,依然繼續(xù)執(zhí)行

[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];

經(jīng)驗(yàn)證,后臺執(zhí)行任務(wù)也將暫停延遲,還是沒辦法解決App長時(shí)間進(jìn)入后臺,定時(shí)器暫停問題。

我們通過監(jiān)聽mainRunLoop回調(diào)可以發(fā)現(xiàn),當(dāng)App進(jìn)入到后臺,mainRunLoop進(jìn)入了休眠,當(dāng)App回到前臺,mainRunLoop重新喚醒繼續(xù)執(zhí)行。

main_runloop_timer.gif

因此我再想,如果在App進(jìn)入后臺的時(shí)候,將已經(jīng)睡眠的mainRunLoop重新喚醒,是不是就可以保證定時(shí)器的不暫停,持續(xù)運(yùn)行。

- (void)didEnterBackground {
    NSLog(@"--------------------------didEnterBackground");
    [[NSRunLoop mainRunLoop] run];
}

main_runloop_background_run_timer.gif

經(jīng)驗(yàn)證,結(jié)果如猜想一樣,在App進(jìn)入后臺,重新喚醒mainRunLoop,可以保證定時(shí)器不暫停,可以一直運(yùn)行。
但詭異的事情發(fā)生了,當(dāng)App重新回到前臺,willEnterForeground回調(diào)也沒走,點(diǎn)擊返回按鍵竟然沒有響應(yīng)。這究竟為什么呢?
找了一些資料才發(fā)現(xiàn):
[[NSRunLoop mainRunLoop] run];并不是喚醒mainRunLoop,而是會使得主線程陷入休眠,永遠(yuǎn)等待,但會讓出主線程時(shí)間片。

[[NSRunLoop mainRunLoop] run]; //主線程永遠(yuǎn)等待,但讓出主線程時(shí)間片
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate distantFuture]]; //等同上面調(diào)用
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate date]]; //立即返回
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //主線程等待,但讓出主線程時(shí)間片,然后過10秒后返回
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; //主線程等待,但讓出主線程時(shí)間片;有事件到達(dá)就返回,比如點(diǎn)擊UI等。
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate date]]; //立即返回
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow:10.0]]; //主線程等待,但讓出主線程時(shí)間片;有事件到達(dá)就返回,如果沒有則過10秒返回。

因此這里我推斷,進(jìn)入后臺之后,調(diào)用[[NSRunLoop mainRunLoop] run];使得主線程陷入休眠,永遠(yuǎn)等待,導(dǎo)致主線程沒有去處理定時(shí)器相關(guān)停止操作,因此定時(shí)器才能繼續(xù)執(zhí)行。

那有沒有辦法可以等回到前臺的時(shí)候,喚醒主線程的呢?

答案是沒有,因?yàn)橹骶€程休眠,導(dǎo)致所有前后臺相關(guān)的通知都不再回調(diào),因此也就沒法區(qū)分App是否回到前臺,也沒辦法去喚醒主線程。

那有沒有方法保證App進(jìn)入后臺之后,定時(shí)器依然能繼續(xù)執(zhí)行呢?

這個(gè)問題我也跟朋友探討了下,正常情況下,App進(jìn)入后臺之后,如果沒有申請持續(xù)更新定位、或者語音等權(quán)限,也沒有通過UIBackgroundTaskIdentifier向系統(tǒng)借用一段時(shí)間,這時(shí)候正常App進(jìn)入后臺就會去中斷其他任務(wù)的執(zhí)行,包括定時(shí)任務(wù),從而掛起,保證系統(tǒng)流暢運(yùn)行。

這也意味著,要先保證App進(jìn)入后臺,定時(shí)器依然能順利執(zhí)行,就要先保證App進(jìn)入后臺依然存活,而不會掛起。

而保證App進(jìn)入后臺的方法,無非就是勾選并實(shí)現(xiàn)對應(yīng)的后臺模式。

除此之外也有一些取巧的方法,如:

  1. App退到后臺
  2. 使用beginBackgroundTaskWithExpirationHandler向系統(tǒng)申請3分鐘的后臺任務(wù),此時(shí)backgroundTimeRemaining=180
  3. 3分鐘限制快到時(shí),啟動定位,此時(shí)3分鐘限制被打破,此時(shí)backgroundTimeRemaining=DBL_MAX
  4. 一小段時(shí)間后(如2s),停止定位,此時(shí)backgroundTimeRemaining≈0
  5. 重復(fù)步驟2

這些方法都是一些取巧的方式,若非必要,不建議采取。

雖然App進(jìn)入后臺后,定時(shí)器會暫停,但是當(dāng)App回到前臺時(shí),定時(shí)器會立馬回調(diào)。因此針對計(jì)時(shí)類的需求,可以在App定時(shí)器啟動之前,記錄一個(gè)當(dāng)前系統(tǒng)時(shí)間的時(shí)間戳preTime,當(dāng)定時(shí)器每次回調(diào),就取當(dāng)前系統(tǒng)時(shí)間戳curTime,然后計(jì)算兩個(gè)時(shí)間戳的差值difTime,然后用需要倒計(jì)時(shí)的時(shí)間totalTime,減去時(shí)間戳差值difTime,就可以算出倒計(jì)時(shí)后的時(shí)間,這樣就能優(yōu)雅的解決,倒計(jì)時(shí)進(jìn)入后臺不準(zhǔn)確的問題。

三. 結(jié)論

關(guān)于iOS定時(shí)器進(jìn)入后臺后在不開啟相關(guān)后臺模式,比如持續(xù)定位更新、音樂播放等模式下,要想保證定時(shí)器進(jìn)入后臺能持續(xù)執(zhí)行,我目前并沒有找到除了一些取巧方法后的好的通用方法。
因此這里對于一些定時(shí)器倒計(jì)時(shí)進(jìn)入后臺不準(zhǔn)確問題,推薦使用如下方案:

App定時(shí)器啟動之前,記錄一個(gè)當(dāng)前系統(tǒng)時(shí)間的時(shí)間戳preTime,當(dāng)定時(shí)器每次回調(diào),就取當(dāng)前系統(tǒng)時(shí)間戳curTime,然后計(jì)算兩個(gè)時(shí)間戳的差值difTime,然后用需要倒計(jì)時(shí)的時(shí)間totalTime,減去時(shí)間戳差值difTime,就可以算出倒計(jì)時(shí)后的時(shí)間,這樣就能優(yōu)雅的解決,倒計(jì)時(shí)進(jìn)入后臺不準(zhǔn)確的問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容