iOS 保证定时器进入后台依然运行
一. 问题背景
最近项目中有个定时器计时
实时更新等车的时长,因为项目里面进入后台是有执行一些任务的操作,因此如果进入后台时间不长,是定时器是不会暂停的,但如果进入后台时间,超过20s
以上,定时器就暂停,回到前台重新开始倒计时,这时候等车的时长会出现不准的情况。
二. 问题原因
经验证NSTimer
,CADisplayLink
,dispatch_source_t
,三个定时器,在进入到后台的时候,都会暂停,等到返回前台的时候,才会继续回调。
看了一些博客说加上后台任务执行这句话可以保证App
进入后台,定时器不会暂停,依然继续执行
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
经验证,后台执行任务也将暂停延迟,还是没办法解决App
长时间进入后台,定时器暂停问题。
我们通过监听mainRunLoop
回调可以发现,当App
进入到后台,mainRunLoop
进入了休眠,当App
回到前台,mainRunLoop
重新唤醒继续执行。
因此我再想,如果在App
进入后台的时候,将已经睡眠的mainRunLoop
重新唤醒,是不是就可以保证定时器的不暂停,持续运行。
- (void)didEnterBackground {
NSLog(@"--------------------------didEnterBackground");
[[NSRunLoop mainRunLoop] run];
}
main_runloop_background_run_timer.gif
经验证,结果如猜想一样,在App
进入后台,重新唤醒mainRunLoop
,可以保证定时器不暂停,可以一直运行。
因此这里我推断,因为我们定时器的回调任务是添加到主队列,由于进入后台,mainRunLoop
进入休眠,导致主线程没有去执行主队列的的任务,因此导致定时器没有回调。
那如果我在子线程开启定时器倒计时,然后通过runloop
保活这个子线程,监听这个子线程的runloop
回调,发现当App
进入后台,子线程的runloop
也进入休眠,这时候子线程的定时器也不再回调.
/// 开启 子线程 倒计时
- (void)startSubThreadNormalTimer {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.countTimer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:true
block:^(NSTimer * _Nonnull timer) {
NSLog(@"--------------------------subThread countDownTimer");
}];
[[NSRunLoop currentRunLoop] addTimer:self.countTimer forMode:NSRunLoopCommonModes];
[self startCurRunloopMonitor];
self.subThreadRunloop = [NSRunLoop currentRunLoop];
[[NSRunLoop currentRunLoop] run];
});
}
sub_thread_runloop_background_run_timer.gif
这时候,我在App
进入后台,单独将子线程的runloop
唤醒,发现子线程的定时器依然不会进行回调。
- (void)didEnterBackground {
NSLog(@"--------------------------didEnterBackground");
[self.subThreadRunloop run];
// [[NSRunLoop mainRunLoop] run];
}
sub_thread_runloop_background_run.gif
但是如果在App
进入后台,单独将主线程的mainRunloop
唤醒,发现子线程的定时器就可以正常执行。
这个现象背后的本质原理是怎样,我找了相关资料,也跟朋友探讨过,依然没有得到一个合理的解释。知道的朋友,可以留言通知下。
三. 结论
如果想让App
进入后台,定时器依然能继续执行,最有效的办法,就是监听App
进入后台的通知,在App
进入后台之后,唤醒主线程的mainRunloop
,也就是加上这句:
[[NSRunLoop mainRunLoop] run];