Keep just one background task

2019-06-07 22:14发布

I'm developing an application that uses a background task to keep tracking of the user position every 20 seconds. All is fine, except that when I enter the application in background, a new background tasks is created, so that I can have in final multiple background tasks that are running. I tried to add [[UIApplication sharedApplication] endBackgroundTask:bgTask]; in applicationWillEnterForeground, but that do nothing. The point is that I want to invalidate/disable all running background tasks when I enter the app and create a new one when I enter in background, or to keep a just one background task running.

- (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
}

2条回答
何必那么认真
2楼-- · 2019-06-07 22:17

I would suggest changing UIBackgroundTaskIdentifier to be a property of the app delegate class and initialize it to UIBackgroundTaskInvalid in didFinishLaunchingWithOptions. Then, in your other app delegate methods, you can just check the value of this property to determine whether there is a background task identifier to end or not.

--

An unrelated observation, but you don't need that runloop stuff. Just schedule the timer on the main thread/runloop (with scheduledTimerWithTimeInterval) and get rid of all of that runloop stuff (because you already added it to the main runloop and that runloop is already running).

For example, let's assume I wanted to do something every 10 seconds while the app was in background, I'd do something like the following:

@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__);
}

Now, in your code sample, the timer wasn't a repeating timer, so if you only wanted to fire the timer once, then set repeats to NO, but then make sure that startTracking then ended the background task (no point in keeping the app alive if you're not going to do anything else).

BTW, make sure you run this code on a device, and do not run it from Xcode (as being attached to Xcode changes the background behavior of apps).

查看更多
神经病院院长
3楼-- · 2019-06-07 22:27

Specify location background mode Use an NSTimer in the background by using UIApplication:beginBackgroundTaskWithExpirationHandler: In case n is smaller than UIApplication:backgroundTimeRemaining ,it will work just fine, in case n is larger, the location manager should be enabled (and disabled) again before there is no time remaining to avoid the background task being killed.

This does work since location is one of the three allowed types of background execution.

Note: Did loose some time by testing this in the simulator where it doesn't work, works fine on phone.

查看更多
登录 后发表回答