📜 ⬆️ ⬇️

Energy-saving background location + sending data to the server from the background

Formulation of the problem

In the application, it is necessary to track the user's location when the application is running in the background (with relatively acceptable accuracy), as well as when the application is active (with high accuracy).

Decision

Solution to the forehead is to use data from callbacks [CLLocationManagerInstance startUpdatingLocation] both in the background and when the application is active. The first and most critical drawback of this solution is high power consumption (in a few hours the iPhone battery can sit down completely). The second is that if the application is minimized and 'killed', we will not be able to receive any updates of the user's position.

To solve these two problems, as well as to make this solution separate and unrelated to the main application code, we write our component that will use [CLLocationManagerInstance startUpdatingLocation] in the active application mode and [CLLocationManagerInstance startMonitorSignificantLocationChanges] in the background. There will be two blocks in the component that will be executed depending on the state of the application.
')
User location

Foreground

For an active application, the solution is obvious - we need to create an instance of CLLocationManager and set a delegate, and then in the callbacks to process the received data. Create a wrapper object:

#import <CoreLocation/CoreLocation.h> typedef void(^locationHandler)(CLLocation *location); @interface DLLocationTracker : NSObject <CLLocationManagerDelegate> @property (nonatomic, strong) CLLocationManager *locationManager; @property (nonatomic, copy) locationHandler locationUpdatedInForeground; - (void)startUpdatingLocation; - (void)stopUpdatingLocation; @end 


The locationUpdatedInForeground block will be executed when the user’s position is updated. The object is created in the controller, then you must call the startUpdatingLocation method to start the service.

Background

As mentioned above, there are two main ways to get updated coordinates in the background:

We use the second approach.
Update the header of our component:

 #import <CoreLocation/CoreLocation.h> typedef void(^locationHandler)(CLLocation *location); @interface DLLocationTracker : NSObject <CLLocationManagerDelegate> @property (nonatomic, strong) CLLocationManager *locationManager; @property (nonatomic, copy) locationHandler locationUpdatedInForeground; @property (nonatomic, copy) locationHandler locationUpdatedInBackground; - (void)startUpdatingLocation; - (void)stopUpdatingLocation; - (void)endBackgroundTask; @end 


The locationUpdatedInBackground block will be called when the application receives the coordinate update in the background.
endBackgroundTask is a method that allows you to complete a task running in the background (we will look at it later).

Also, you need to add the item Required background modes = {App registers for location updates} to * .plist of the application.

The Significant Location Changes mechanism allows you to receive location updates even if the application is not running. To do this, you need to slightly rewrite the standard appDelegate applicationDidFinishLaunchingWithOptions method:

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) { self.locationTracker = [[DLLocationTracker alloc] init]; [self.locationTracker setLocationUpdatedInBackground:^(CLLocation *location) { // ,   local notification   UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:15]; notification.alertBody = [NSString stringWithFormat:@"New location: %@", location]; [[UIApplication sharedApplication] scheduleLocalNotification:notification]; }]; [self.locationTracker startUpdatingLocation]; } ..... } 


UIApplicationLaunchOptionsLocationKey is a key that indicates that the application was launched in response to a received location change event.

Component implementation

When the component is initialized, the CLLocationManager instance is created and the object is set by its delegate, and we also subscribe it to the notification about the change in the state of the application (active / background).

 - (id)init { if (self = [super init]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground) name: UIApplicationDidEnterBackgroundNotification object:nil]; self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.delegate = self; } return self; } 


Next we call startUpdatingLocation:

 - (void)startUpdatingLocation { [self stopUpdatingLocation]; [self isInBackground] ? [self.locationManager startMonitoringSignificantLocationChanges] : [self.locationManager startUpdatingLocation]; } 

Depending on the application status, the necessary service is activated.
The most interesting thing happens in the CLLocationManager callback:

 - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { //          if (oldLocation && ([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp] < kMinUpdateTime || [newLocation distanceFromLocation:oldLocation] < kMinUpdateDistance)) { return; } if ([self isInBackground]) { if (self.locationUpdatedInBackground) { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{ [[UIApplication sharedApplication] endBackgroundTask:bgTask]; }]; self.locationUpdatedInBackground(newLocation); [self endBackgroundTask]; } } else { //   -    if (self.locationUpdatedInForeground) { self.locationUpdatedInForeground(newLocation); } } } 

In order for our application to do something in the background, you must call the beginBackgroundTaskWithExpirationHandler method and initialize the bgTask identifier (type UIBackgroundTaskIdentifier). Each call to this method must be balanced by calling endBackgroundTask :, which happens in [self endBackgroundTask] :

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

The important point is that the locationUpdatedInBackground block runs synchronously (we can afford it when the applications are in the background), this can cause problems if the application is minimized / expanded during the execution of the block, namely, if the block is not executed within 10 seconds application will crash.

Asynchronous sending of background data

To send asynchronously a little, change the code of our component:

  if ([self isInBackground]) { if (self.locationUpdatedInBackground) { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{ [[UIApplication sharedApplication] endBackgroundTask:bgTask]; }]; self.locationUpdatedInBackground(newLocation); //[self endBackgroundTask]; -            } 

Block locationUpdatedInBackground :

  __weak DLLocationTracker *lc = self.locationTracker; [self.locationTracker setLocationUpdatedInBackground:^ (CLLocation *location) { //,       completion  fail     [self sendLocationToServer:location completion:^{ [lc endBackGroundTask]; } fail:^(NSError *fail) { [lc endBackGroundTask]; }]; }]; 


Conclusion

This energy-efficient method is used in many applications. For example, the Radar feature in Forsquare. The test application code can be taken on github .

Source: https://habr.com/ru/post/149836/


All Articles