Solution to the problem of timer and location update stopping after iOS application enters the background

  • 2021-11-24 03:06:00
  • OfStack

Because the iOS system runs in a "pseudo-background" mode, When the HOME key is pressed, If the program does not do anything, The application will have an execution buffer time of 5 seconds, and the random program will be suspended. All task terminals, including timers and location updates, can be executed in the background after the program turns on the background mode switch, such as audio, positioning, Bluetooth, download and VOIP. Even so, the background operation of the program can be extended by up to 594 seconds (about 10 minutes). Unfortunately, a program that declares a background mode is likely to be rejected when it hits the shelves of app. Based on this, I developed a way to keep the timer and positioning running when app enters the foreground without declaring the background mode.

The implementation principle is as follows:

Using the notification mechanism of iOS, Send a notification when the program enters the background and returns to the foreground again, And record the current time of entering the background and the current time of returning to the foreground again, calculate the time interval between the two, add a notice listener at any place needed by the program, execute a code block in the listening method, and the parameters in the code block are the notice object and the calculated time interval. Taking the timer as an example, after the program enters the background again, the timer stops running. At this time, when the program returns to the foreground again, the content in the code block is executed by using the above method, and the current time interval of the timer when the program enters the background is added with the time interval parameter of the code block, so that the timer can accurately time. Needless to say, put on the code:

In the AppDelegate. m implementation file:


- (void)applicationDidEnterBackground:(UIApplication *)application {
  // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
  // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
  [[NSNotificationCenter defaultCenter]postNotificationName:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
  // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
  [[NSNotificationCenter defaultCenter]postNotificationName:UIApplicationWillEnterForegroundNotification object:nil];
}

Code Description: After the program enters the background, use the system notification mechanism to inform the program to enter the background and return to the foreground again, monitoring objects for all objects.

After that, define a handler to enter the background class YTHandlerEnterBackground


//
// YTHandlerEnterBackground.h
//  Time-sharing lease 
//
// Created by  Koch's spectrum  on 17/2/24.
// Copyright © 2017 Year   Koch's spectrum . All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**  Enter the background block typedef */
typedef void(^YTHandlerEnterBackgroundBlock)(NSNotification * _Nonnull note, NSTimeInterval stayBackgroundTime);
/**  Processing goes into the background and calculates the time interval class left in the background  */
@interface YTHandlerEnterBackground : NSObject
/**  Add observers and process the background  */
+ (void)addObserverUsingBlock:(nullable YTHandlerEnterBackgroundBlock)block;
/**  Remove Background Observer  */
+ (void)removeNotificationObserver:(nullable id)observer;
@end

In the YTHandlerEnterBackground. m implementation file:


//
// YTHandlerEnterBackground.m
//  Time-sharing lease 
//
// Created by  Koch's spectrum  on 17/2/24.
// Copyright © 2017 Year   Koch's spectrum . All rights reserved.
//
#import "YTHandlerEnterBackground.h"
@implementation YTHandlerEnterBackground
+ (void)addObserverUsingBlock:(YTHandlerEnterBackgroundBlock)block {
  __block CFAbsoluteTime enterBackgroundTime;
  [[NSNotificationCenter defaultCenter]addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
    if (![note.object isKindOfClass:[UIApplication class]]) {
      enterBackgroundTime = CFAbsoluteTimeGetCurrent();
    }
  }];
  __block CFAbsoluteTime enterForegroundTime;
  [[NSNotificationCenter defaultCenter]addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
    if (![note.object isKindOfClass:[UIApplication class]]) {
      enterForegroundTime = CFAbsoluteTimeGetCurrent();
      CFAbsoluteTime timeInterval = enterForegroundTime-enterBackgroundTime;
      block? block(note, timeInterval): nil;
    }
  }];
}
+ (void)removeNotificationObserver:(id)observer {
  if (!observer) {
    return;
  }
  [[NSNotificationCenter defaultCenter]removeObserver:observer name:UIApplicationDidEnterBackgroundNotification object:nil];
  [[NSNotificationCenter defaultCenter]removeObserver:observer name:UIApplicationWillEnterForegroundNotification object:nil];
}
@end

This class implements methods for adding notification listeners and handling background and removing notification listeners. Note that in the addObserverUsingBlock method, there must be if (! [note. object isKindOfClass: [UIApplication class]]), otherwise the code block in the addObserverForName method executes multiple times, and this code executes twice. The addObserverUsingBlock method calls the Add Notification Listener in the viewWillAppear method and the Remove Notification Listener in the viewWillDisappear method.

For example, in an NSTimer controller that uses a timer:


- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  [YTHandlerEnterBackground addObserverUsingBlock:^(NSNotification * _Nonnull note, NSTimeInterval stayBackgroundTime) {
    self.rentTimerInterval = self.rentTimerInterval-stayBackgroundTime;
  }];
}
- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];
  [self.timer invalidate];
  [YTHandlerEnterBackground removeNotificationObserver:self];
}

I defined a timer object timer property with a countdown of 5 minutes. The current countdown time interval rentTimerInterval attribute of a timer is defined. In the code block of adding notification listener, rentTimerInterval is equal to the countdown time interval when entering the background minus the time interval when the program stays in the background. When the timer returns to the foreground again, the time interval of the timer at this time is continuous. Although the timer does not run continuously in the background, using this method, the timer is also realized correctly and immediately.

Similarly, when the program has the location update function, when the program enters the background, the location service object will automatically stop updating. At this time, the practice is still to call the above two methods for processing entering the background, so that after the program enters the background, it starts positioning again:

In classes that require location updates:


- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  self.locService.delegate = self;
  [self.locService startUserLocationService];
  // Enter the background and then enter the foreground to restart positioning 
  [YTHandlerEnterBackground addObserverUsingBlock:^(NSNotification * _Nonnull note, NSTimeInterval stayBackgroundTime) {
    [self.locService startUserLocationService];
  }];
}
- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];
  // Stop positioning 
  self.locService.delegate = nil;
  [self.locService stopUserLocationService];
  // Remove background listening 
  [YTHandlerEnterBackground removeNotificationObserver:self];
}

Baidu map SDK is used here

With this method, Tasks that need to run in the background, such as timers and location updates, can meet the corresponding requirements. It's just troublesome to call these two methods in any class you need. You can add other parameters when the program enters the background and returns to the foreground again according to your own needs (it is necessary to inform the object parameters), such as saving the operation before entering the background and so on. Or define different ways to add notification listeners to meet different requirements.


Related articles: