一. 問題背景
最近項目中有個定時器計時
實時更新等車的時長续挟,因為項目里面進入后臺是有執(zhí)行一些任務(wù)的操作跑芳,因此如果進入后臺時間不長博个,是定時器是不會暫停的往堡,但如果進入后臺時間投蝉,超過20s
以上关拒,定時器就暫停谐算,回到前臺重新開始倒計時洲脂,這時候等車的時長會出現(xiàn)不準的情況。
二. 問題原因
經(jīng)驗證NSTimer
,CADisplayLink
一铅,dispatch_source_t
,三個定時器,在進入到后臺的時候卜录,都會暫停艰毒,等到返回前臺的時候凯傲,才會繼續(xù)回調(diào)冰单。
看了一些博客說加上后臺任務(wù)執(zhí)行這句話可以保證App
進入后臺浴栽,定時器不會暫停被廓,依然繼續(xù)執(zhí)行
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
經(jīng)驗證嫁乘,后臺執(zhí)行任務(wù)也將暫停延遲蜓斧,還是沒辦法解決App
長時間進入后臺,定時器暫停問題直奋。
我們通過監(jiān)聽mainRunLoop
回調(diào)可以發(fā)現(xiàn)帮碰,當App
進入到后臺殉挽,mainRunLoop
進入了休眠,當App
回到前臺傻唾,mainRunLoop
重新喚醒繼續(xù)執(zhí)行伪煤。
因此我再想,如果在App
進入后臺的時候防泵,將已經(jīng)睡眠的mainRunLoop
重新喚醒捷泞,是不是就可以保證定時器的不暫停,持續(xù)運行骡湖。
- (void)didEnterBackground {
NSLog(@"--------------------------didEnterBackground");
[[NSRunLoop mainRunLoop] run];
}
經(jīng)驗證惠桃,結(jié)果如猜想一樣辜王,在App
進入后臺,重新喚醒mainRunLoop
汹来,可以保證定時器不暫停收班,可以一直運行。
但詭異的事情發(fā)生了邻耕,當App
重新回到前臺兄世,willEnterForeground
回調(diào)也沒走碘饼,點擊返回按鍵竟然沒有響應(yīng)艾恼。這究竟為什么呢舆声?
找了一些資料才發(fā)現(xiàn):
[[NSRunLoop mainRunLoop] run];
并不是喚醒mainRunLoop
,而是會使得主線程陷入休眠蛾找,永遠等待打毛,但會讓出主線程時間片。
[[NSRunLoop mainRunLoop] run]; //主線程永遠等待熬甫,但讓出主線程時間片
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate distantFuture]]; //等同上面調(diào)用
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate date]]; //立即返回
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //主線程等待,但讓出主線程時間片覆旱,然后過10秒后返回
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; //主線程等待,但讓出主線程時間片炼彪;有事件到達就返回辐马,比如點擊UI等。
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate date]]; //立即返回
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow:10.0]]; //主線程等待檩帐,但讓出主線程時間片;有事件到達就返回四敞,如果沒有則過10秒返回达箍。
因此這里我推斷缎玫,進入后臺之后,調(diào)用[[NSRunLoop mainRunLoop] run];
使得主線程陷入休眠,永遠等待,導(dǎo)致主線程沒有去處理定時器相關(guān)停止操作缕减,因此定時器才能繼續(xù)執(zhí)行。
那有沒有辦法可以等回到前臺的時候部逮,喚醒主線程的呢兄朋?
答案是沒有,因為主線程休眠怜械,導(dǎo)致所有前后臺相關(guān)的通知都不再回調(diào)颅和,因此也就沒法區(qū)分App
是否回到前臺,也沒辦法去喚醒主線程缕允。
那有沒有方法保證App
進入后臺之后峡扩,定時器依然能繼續(xù)執(zhí)行呢?
這個問題我也跟朋友探討了下灼芭,正常情況下寄悯,App
進入后臺之后洒擦,如果沒有申請持續(xù)更新定位椅邓、或者語音等權(quán)限绰精,也沒有通過UIBackgroundTaskIdentifier
向系統(tǒng)借用一段時間,這時候正常App
進入后臺就會去中斷其他任務(wù)的執(zhí)行门岔,包括定時任務(wù),從而掛起好渠,保證系統(tǒng)流暢運行拳锚。
這也意味著棋凳,要先保證App
進入后臺,定時器依然能順利執(zhí)行尚困,就要先保證App
進入后臺依然存活,而不會掛起邻悬。
而保證App
進入后臺的方法,無非就是勾選并實現(xiàn)對應(yīng)的后臺模式蘑斧。
除此之外也有一些取巧的方法花颗,如:
-
App
退到后臺 - 使用
beginBackgroundTaskWithExpirationHandler
向系統(tǒng)申請3
分鐘的后臺任務(wù),此時backgroundTimeRemaining=180
-
3
分鐘限制快到時域携,啟動定位旋圆,此時3
分鐘限制被打破,此時backgroundTimeRemaining=DBL_MAX
- 一小段時間后(如
2s
),停止定位屋匕,此時backgroundTimeRemaining≈0
- 重復(fù)步驟2
這些方法都是一些取巧的方式翩隧,若非必要,不建議采取。
雖然App
進入后臺后茂翔,定時器會暫停,但是當App
回到前臺時迁沫,定時器會立馬回調(diào)。因此針對計時類的需求祈餐,可以在App
定時器啟動之前绷杜,記錄一個當前系統(tǒng)時間的時間戳preTime
,當定時器每次回調(diào)歇竟,就取當前系統(tǒng)時間戳curTime
懊烤,然后計算兩個時間戳的差值difTime
弃秆,然后用需要倒計時的時間totalTime
们拙,減去時間戳差值difTime
,就可以算出倒計時后的時間,這樣就能優(yōu)雅的解決坷虑,倒計時進入后臺不準確的問題账磺。
三. 結(jié)論
關(guān)于iOS
定時器進入后臺后在不開啟相關(guān)后臺模式氏捞,比如持續(xù)定位更新辞嗡、音樂播放等模式下,要想保證定時器進入后臺能持續(xù)執(zhí)行,我目前并沒有找到除了一些取巧方法后的好的通用方法达址。
因此這里對于一些定時器倒計時進入后臺不準確問題苛败,推薦使用如下方案:
在App
定時器啟動之前,記錄一個當前系統(tǒng)時間的時間戳preTime
译蒂,當定時器每次回調(diào)炎辨,就取當前系統(tǒng)時間戳curTime
,然后計算兩個時間戳的差值difTime
,然后用需要倒計時的時間totalTime
曹货,減去時間戳差值difTime
,就可以算出倒計時后的時間,這樣就能優(yōu)雅的解決,倒計時進入后臺不準確的問題。