只保留一个后台任务

Keep just one background task

我正在开发一个应用程序,它使用后台任务每 20 秒跟踪一次用户位置。一切都很好,除了当我在后台进入应用程序时,会创建一个新的后台任务,这样我就可以在最终的多个后台任务中得到 运行。 我试图在 applicationWillEnterForeground 中添加 [[UIApplication sharedApplication] endBackgroundTask:bgTask];,但那没有任何作用。 关键是我想在进入应用程序时 invalidate/disable 所有 运行 后台任务,并在进入后台时创建一个新任务,或者只保留一个后台任务 运行。

- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self runBackgroundTask:10];
}

-(void)runBackgroundTask: (int) time{
    //check if application is in background mode
    if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {

        __block UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
            [[UIApplication sharedApplication] endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:@selector(startTracking) userInfo:nil repeats:NO];
            [[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
            [[NSRunLoop currentRunLoop] run];
        });
    }
}

-(void)startTracking{
//Location manager code that is running well
}

指定位置背景模式 使用 UIApplication:beginBackgroundTaskWithExpirationHandler: 在后台使用 NSTimer 如果 n 小于 UIApplication:backgroundTimeRemaining ,它会工作得很好,如果 n 更大, location manager 应该在没有之前再次启用(和禁用)避免后台任务被杀死的剩余时间。

这确实有效,因为位置是三种允许的后台执行类型之一。

注意:通过在模拟器中测试它不起作用而浪费了一些时间,在 phone 上运行良好。

我建议将 UIBackgroundTaskIdentifier 更改为应用委托 class 的 属性,并在 didFinishLaunchingWithOptions 中将其初始化为 UIBackgroundTaskInvalid。然后,在你的其他应用程序委托方法中,你可以只检查这个 属性 的值来确定是否有后台任务标识符结束。

--

一个不相关的观察,但您不需要 运行循环的东西。只需在主 thread/runloop 上安排计时器(使用 scheduledTimerWithTimeInterval)并摆脱所有 运行 循环的东西(因为你已经将它添加到主 运行 循环和运行loop 已经 运行ning).

例如,假设我想在应用程序处于后台时每 10 秒执行一次操作,我会执行如下操作:

@interface AppDelegate ()

@property (atomic) UIBackgroundTaskIdentifier bgTask;
@property (nonatomic, weak) NSTimer *timer;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.bgTask = UIBackgroundTaskInvalid;
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        if (self.bgTask != UIBackgroundTaskInvalid) {
            [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
            self.bgTask = UIBackgroundTaskInvalid;
        }
    }];

    self.timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(startTracking) userInfo:nil repeats:YES];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // invalidate the timer if still running

    [self.timer invalidate];

    // end the background task if still present

    if (self.bgTask != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    }
}

- (void)startTracking{
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

现在,在您的代码示例中,计时器不是重复计时器,因此如果您只想触发计时器一次,请将 repeats 设置为 NO,但随后要确保startTracking 然后结束了后台任务(如果您不打算做任何其他事情,就没有必要让应用程序保持活动状态)。

顺便说一句,确保你在设备上 运行 这段代码,而不是 运行 来自 Xcode 的代码(因为附加到 Xcode 会改变背景行为的应用程序)。