iOS后台任务beginBackgroundTask和endBackgroundTask成对出现

Posted 想名真难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS后台任务beginBackgroundTask和endBackgroundTask成对出现相关的知识,希望对你有一定的参考价值。

接到线上有人反馈我们的app经常在后台被杀死, 一开始以为是系统机制问题, 后来发现很大程度上是beginBackgroundTask && endBackgroundTask 没有使用正确, 导致后台任务超时, 然后被系统kill. 

1.标准写法

@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTask;


// APP进入后台的通知
- (void)applicationDidEnterBackgroundWithNote:(NSNotification *)note 
    [self _beginBackgroundTask];

 
// APP回到前台的通知
- (void)applicationWillEnterForegroundWithNote:(NSNotification *)note 
    [self _endBackgroundTaskIfNeeded];

 
 
- (void)_beginBackgroundTask 
    [self _endBackgroundTaskIfNeeded];
     
    _backgroundTask = [UIApplication.sharedApplication beginBackgroundTaskWithExpirationHandler:^
        [self _endBackgroundTaskIfNeeded];
    ];
    

 
- (void)_endBackgroundTaskIfNeeded 
    if ( _backgroundTask != UIBackgroundTaskInvalid ) 
        [UIApplication.sharedApplication endBackgroundTask:_backgroundTask];
        _backgroundTask = UIBackgroundTaskInvalid;
    

可以通过打印UIApplication.sharedApplication.backgroundTimeRemaining来看最大的后台运行时长,在不同的ios系统上不太一样, 大部分是180秒。

注意:测试此功能不能用Xcode直接debug运行,因为在调试器链接到app的进行的情况下,app是不会在后台被挂起的。

2.是否能递归调用此方法来持续获得执行时间

在beginBackgroundTaskWithExpirationHandler里最后再递归调用[self startTask];

经尝试此方法无效,180秒超时后再次申请,会立刻回调超时的block,并且backgroundTimeRemaining时间一直都是0。

并且由于一直不停的在递归创建和终止后台任务,当Expiration真正到来的时候,一个还有一个创建的任务没有关闭。从而导致违背begin和end成对调用的原则,app被系统强制kill。所以此方法不但不能延长执行时间,还会导致app在180秒后台执行时间到达后,被系统kill的情况。

3.beginBackgroundTaskWithExpirationHandler多次被调用的情况

didEnterBackground每次调用都会触发beginBackgroundTaskWithExpirationHandler来创建新的后台任务,并用backgroundUpdateTask保存任务id,但如果第一次的任务还没有endBackgroundTask之前,应用回到前台,然后再次进入后台,就会重新创建一个新的后台任务,并且backgroundUpdateTask之前保存的id会被覆盖,这就违背了beginBackgroundTaskWithExpirationHandler与endBackgroundTask成对调用的原因。

因为前一个后台任务超时的block回调的时候,其实是end了后一个taskId对应的后台任务,并且把taskId赋值为UIBackgroundTaskInvalid。而后一个后台任务超时的block回调的时候,taskId已经变成了null,对其进行end调用已经无效了,所以相当于没有成对调用begin和end,导致的结果就是:后一个后台任务超时的时候,app被系统强制kill。

所以每一次创建的后台任务都要有一个独立的变量来维护其taskId,如果只有一个后台任务,但是有重入的可能,那么应该在willEnterForeground回调中,把前一个后台任务进行endBackgroundTask操作,这样就不存在taskId被覆盖的问题了。或者是每次didEnterBackground的时候,检查taskId == UIBackgroundTaskInvalid,若不满足该条件,说明taskId已经引用了一个正在进行的后台任务,还没有完成,由于这个后台任务重进前台又切换回后台的情况下,backgroundTimeRemaining会被重置为180秒,所以在这种情况下,也可以直接return

if(backgroundUpdateTask != UIBackgroundTaskInvalid)

    return;

4、检查其他三方SDK是否存在问题

此时可以保证我们自己的写法是没有问题的了, 但是很多三方SDK也做了保活措施, SDK大多是通过系统通知来监听的, 需要检查下SDK的调用是否成对出现, 那么就需要使用runtime来hook系统的方法, 一共有3个方法需要hook, 2个开启, 一个结束

  • - beginBackgroundTaskWithExpirationHandler:
  • - beginBackgroundTaskWithName:expirationHandler:
  • -endBackgroundTask:

在hook之后, 可以拿一个字典来记录, 使用任务ID作为Key, 开启任务保存此任务ID到数组中, 结束任务从字典中移除此ID, 在app的各种场景下进前台,退后台, 查看开启和结束是否成对, 找出没有成对的地方, 打印堆栈信息, 找出没有成对的SDK, 升级SDK或者替换掉.

- (UIBackgroundTaskIdentifier)gcs_beginBackgroundTaskWithExpirationHandler:(void(^ __nullable)(void))handler 
    UIBackgroundTaskIdentifier identifier = [self gcs_beginBackgroundTaskWithExpirationHandler:handler];
    NSLog(@"%s, 开启任务ID: %@", __func__, @(identifier));
    NSString *key = [self taskKeyWithTaskIdentifier:identifier];
    leftBackgroundTaskDic[key] = [self taskInfo];
    return identifier;


- (UIBackgroundTaskIdentifier)gcs_beginBackgroundTaskWithName:(nullable NSString *)taskName expirationHandler:(void(^ __nullable)(void))handler 
    UIBackgroundTaskIdentifier identifier = [self gcs_beginBackgroundTaskWithName:taskName expirationHandler:handler];
    NSLog(@"%s, 开启任务名称:%@, ID: %@", __func__,taskName, @(identifier));
    NSString *key = [self taskKeyWithTaskIdentifier:identifier];
    leftBackgroundTaskDic[key] = [self taskInfo];
    return identifier;


- (void)gcs_endBackgroundTask:(UIBackgroundTaskIdentifier)identifier 
    NSLog(@"%s, 结束任务ID: %@", __func__, @(identifier));
    [self gcs_endBackgroundTask:identifier];
    NSString *key = [self taskKeyWithTaskIdentifier:identifier];
    leftBackgroundTaskDic[key] = nil;

以上是关于iOS后台任务beginBackgroundTask和endBackgroundTask成对出现的主要内容,如果未能解决你的问题,请参考以下文章

iOS的后台任务

iOS后台运行任务

关于ios中后台长时间下载任务的实现与走过的坑

iOS 13 安排 iOS 后台任务

iOS 后台任务

IOS 13中的iOS后台任务不起作用