In the previous article I've talked about the basic location api and I've introduced the geo-fencing features that are new in Windows 8.1. These API, partly already available in the very first release of the operating system, are for sure a interesting opportunity to build location aware applications, taking the full advantage from the "mobility" of modern devices.
The API that I've explained, are all made to be used when you application is running, but as you probably figured out, this is not exactly the most common situation for our applications.
Location aware background tasks
If you try to stop and think about the lifetime of an application, you probably will find that also when they are used a lot, for the major part of their life they are switched off, probably for more time than they are running. In a tracking application that should collect positions of the device along the time, this is not an ideal situation.
If you think that a Windows 8.1 device can run a maximum of 4 applications at a time, under particolar conditions, you understand that it is completely unacceptable to track positions only when the application is running. Even when snapped, there are a number of events that can make your app going to sleep and then stopping to track.
For this reason with Windows 8.1 they added the capability of tracking the position in a background task. Just for remember, a background task is a separate task that is started when the application is run for the first time and then it remains active also if the application has been closed or has been put to sleep. Together with the common background task that we know in Windows 8, the new relase added a new type that is dedicated to location services. To activate this kind of background service you have to open the manifest in Visual Studio and go to the "Declarations" tab. Then add a "Background Service" and select the type on the right side.
I'll return in a few on the registration topic. This is infact the last action you have to accomplish just before deploy the application and try to run. Before this you have to phisically create the background task. For location purposes, there are two type of tasks you can use: a "Timer", used to collect locations as an example, and a "Location" task that is mostly used to support geofencing. Both the types must be created using a class declared into a Windows Runtime Component. This is an important point because nothing prevents to create the task in a class library but is will not work as expected, so please be always careful in this point.
So first of all let create a Windows Runtime Component, remember to reference it from your application project and then you can create your background task skeleton inside of it:
1: public sealed class LocationService : IBackgroundTask
2: {
3: public void Run(IBackgroundTaskInstance taskInstance)
4: {
5: var deferral = taskInstance.GetDeferral();
6:
7: try
8: {
9: // do the work here...
10: }
11: finally
12: {
13: deferral.Complete();
14: }
15: }
16: }
The background task, when invoked take a reference to a deferral. This is used to communicate to the runtime when the task has completed but remember, it does not give you more time than the few seconds granted by the operating system. Then you have to manually register the background task in code. You call your registration service when the user asks to run the background service. You have always to create also the code to unregister the background task in the case the user would not want to run the background task anymore.
1: private const string LocationServiceName = "LocationService";
2: private const string LocationServiceEntryPoint = "XPG.Geolocation.Tasks.LocationService";
3:
4: private IBackgroundTaskRegistration GeoLocationTask { get; set; }
5:
6: private async void RegisterBackgroundTask()
7: {
8: this.GeoLocationTask = BackgroundTaskRegistration.AllTasks.FirstOrDefault(
9: bt => bt.Value.Name == LocationServiceName).Value;
10:
11: if (this.GeoLocationTask == null)
12: {
13: BackgroundAccessStatus backgroundAccessStatus =
14: await BackgroundExecutionManager.RequestAccessAsync();
15:
16: if (backgroundAccessStatus == BackgroundAccessStatus.Unspecified ||
17: backgroundAccessStatus == BackgroundAccessStatus.Denied)
18: throw new Exception("Error registering background task: not authorized");
19:
20: BackgroundTaskBuilder taskBuilder = new BackgroundTaskBuilder();
21:
22: taskBuilder.Name = LocationServiceName;
23: taskBuilder.TaskEntryPoint = LocationServiceEntryPoint;
24:
25: TimeTrigger timeTrigger = new TimeTrigger(15, false);
26:
27: taskBuilder.SetTrigger(timeTrigger);
28:
29: this.GeoLocationTask = taskBuilder.Register();
30: }
31:
32: this.GeoLocationTask.Completed += OnCompleted;
33: }
34:
35: private void UnregisterBackgroundTask()
36: {
37: if (this.GeoLocationTask != null)
38: {
39: this.GeoLocationTask.Completed -= OnCompleted;
40: this.GeoLocationTask.Unregister(true);
41: this.GeoLocationTask = null;
42: }
43: }
44:
45: private void OnCompleted(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
46: {
47: Debug.WriteLine("Task {0} has completed", sender.Name);
48: }
In the very first lines of code I check if the task has already registered. This is important to avoid duplicate instances that can lead to confusion while running and debugging. Then, if the task has not registered, I request the access to background services. This presents to the user the common request that asks if he consents to run the task in background. In the case the user denies we have to gracefully exit from registration and continue to run the application. Differently, using the BackgroundTaskBuilder the task is created and registered. Pay attention to the name of the task and to the Task entry point because is it often cause of not working tasks. Associated to this task there is a TimeTrigger, that will raise the task once every 15 minutes. The geo location task has also a completed event that is useful to update the user interface when the application is running and the task completes its work. Inside the Location task I get the location and store it wherever I want:
1: private CancellationTokenSource CancellationTokenSource { get; set; }
2:
3: public async void Run(IBackgroundTaskInstance taskInstance)
4: {
5: var deferral = taskInstance.GetDeferral();
6:
7: try
8: {
9: if (this.CancellationTokenSource == null)
10: this.CancellationTokenSource = new CancellationTokenSource();
11:
12: taskInstance.Canceled += taskInstance_Canceled;
13:
14: Geolocator locator = new Geolocator();
15:
16: Geoposition position = await locator.GetGeopositionAsync()
17: .AsTask(this.CancellationTokenSource.Token);
18:
19: this.StoreLastPosition(DateTime.Now, position.Coordinate.Point);
20: }
21: catch (Exception ex)
22: {
23: Debug.WriteLine(ex.Message);
24: }
25: finally
26: {
27: taskInstance.Canceled -= taskInstance_Canceled;
28: deferral.Complete();
29: }
30: }
31:
32: private void taskInstance_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
33: {
34: if (this.CancellationTokenSource != null)
35: {
36: this.CancellationTokenSource.Cancel();
37: this.CancellationTokenSource = null;
38: }
39: }
40:
41: private void StoreLastPosition(DateTime dateTime, Geopoint geopoint)
42: {
43: }
Inside the Run method a CancellationToken is created to be able to stop che geo location task if a cancellation occurs before the operation is completed. Given that a background task can run only for a limited time, it is required to always have an handle that allows to stop the asynchronous operation if it is required. In the Cancel event the CancellationToken is Canceled causing the Task to exit.
Geofencing in background
Upto here we have implemented a background task using a TimeTrigger. This kind of trigger is not new to Windows 8.1. In the new release of the operating system they added also a LocationTrigger. A LocationTrigger is strictly related to the geofencing system. It triggers the background task execution when the status of a geofence, registered with the GeofenceMonitor has changed. This is really awesome give that we can use the GeofenceMonitor as I've explained in the previous number of this series and then attach a LocationTrigger to a background task and get notifications when the device enters or exits the geofences. Here is how to register the Location trigger.
1: private async void RegisterGeofenceBackgroundTask()
2: {
3: this.GeoLocationTask = BackgroundTaskRegistration.AllTasks.FirstOrDefault(
4: bt => bt.Value.Name == GeofenceServiceName).Value;
5:
6: if (this.GeoFenceTask == null)
7: {
8: BackgroundAccessStatus backgroundAccessStatus =
9: await BackgroundExecutionManager.RequestAccessAsync();
10:
11: if (backgroundAccessStatus == BackgroundAccessStatus.Unspecified ||
12: backgroundAccessStatus == BackgroundAccessStatus.Denied)
13: throw new Exception("Error registering background task: not authorized");
14:
15: BackgroundTaskBuilder taskBuilder = new BackgroundTaskBuilder();
16:
17: taskBuilder.Name = GeofenceServiceName;
18: taskBuilder.TaskEntryPoint = GeofenceServiceEntryPoint;
19:
20: LocationTrigger locationTrigger = new LocationTrigger(LocationTriggerType.Geofence);
21:
22: taskBuilder.SetTrigger(locationTrigger);
23:
24: this.GeoFenceTask = taskBuilder.Register();
25: }
26:
27: this.GeoFenceTask.Completed += OnCompleted;
28: }
The code is pretty similar to the one used for a TimeTrigger but it differs for the declaration of the LocationTrigger. This kind of trigger only accept a LocationTriggerType equals to Geofence that is the sole available at the moment. Here we do not declare any geofence to monitor. These have to be already added to the geofence monitor previously. So the trigger will fire when the monitor raises an event of GeofenceStateChanged.
The background task is in charge of finding what is the geofences that have been affected by this change. For this purpose you can use the ReadReports method.
1: public sealed class GeofenceService : IBackgroundTask
2: {
3: private CancellationTokenSource CancellationTokenSource { get; set; }
4:
5: public void Run(IBackgroundTaskInstance taskInstance)
6: {
7: var deferral = taskInstance.GetDeferral();
8:
9: try
10: {
11: this.EvaluateGeofences();
12: }
13: catch (Exception ex)
14: {
15: Debug.WriteLine(ex.Message);
16: }
17: finally
18: {
19: deferral.Complete();
20: }
21: }
22:
23: private void EvaluateGeofences()
24: {
25: foreach (var report in GeofenceMonitor.Current.ReadReports())
26: {
27: Debug.WriteLine("Geofence {0} changed to {1}", report.Geofence.Id, report.NewState);
28: }
29: }
30: }
The ReadReports method returns all the changes occured since last time the method was called. This is really perfect to get a list of events that we can evaluate to understand what is the one that has triggered the background service.
Improved location-awareness
In this article and in the previous, I've explained how to take advantage of location services that are exposed by Windows 8 and recently powered by the 8.1 release. It is sure that the recent release added some very important trick for the development of location-aware apps. The geofencing is really a beautiful feature that can lead to a number of applications, also for business purposes. I hope that next releases also add some more control to the location aspects, to give another major gear to this category of apps.