Skip to content

Philosophy of Operation

Chris Scott edited this page Apr 18, 2024 · 10 revisions

The core philosophy of Background Geolocation is to track a device's location in the most battery-efficient manner possible. For this reason, motion-detection (detecting when the device is still vs moving) is central to this philosophy. Only when the device is detected to be moving will the plugin engage location-services. When the device is sitting still, location-services are off.

Two States: Moving & Stationary

The plugin has two states: moving & stationary. The plugin automatically toggles between these states by monitoring the native MotionActivity API as well as a geofence around the last known position. The MotionActivity API is capable of detecting when the device is still, on_foot, running, on_bicycle and in_vehicle. When the plugin detects the motion-activity of still, the plugin will enter the stationary state. When any motion-type activity is detected (eg: on_foot), the plugin will enter the moving state, turn location-services on and begin recording a location each distanceFilter meters. As a backup motion-detection mechanism, the plugin also monitors a geofence of radius 200 meters around the last known position — when the device exits this 200 meter geofence, the device will enter the moving state.

You can listen to these state-changes by subscribing to the onMotionChange event:

BackgroundGeolocation.onMotionChange((location) => {
  console.log("[onMotionChange] isMoving?", location.isMoving);
});

Manually Toggling Between States

The plugin can manually be toggled between states by using the method #changePace. This method accepts a boolean, where true will change state -> moving and false -> stationary.

BackgroundGeolocation.changePace(true).then(() => {
  console.log('[BackgroundGeolocation] is in the moving state');
});
.
.
.
await BackgroundGeolocation.changePace(false).then(() => {
  console.log('[BackgroundGeolocation] is in the stationary state');
});

stopTimeout

When the MotionActivity API first reports an activity of still while in the moving state, the plugin will engage its "stop-detection" system. This system involves initiating a timer of stopTimeout minutes. When this timer finally expires, the plugin will enter the stationary state. If the MotionActivity API reports a moving-type activity before the timer times-out, the stopTimeout timer will be cancelled and stop-detection will cease -- the plugin will remain in the moving state.

Location Authorization: WhenInUse vs Always

By default, the plugin requests Always location (See API docs Config.locationAuthorizationRequest.

While the plugin can work with WhenInUse authorization, there are major consequences to doing so:

  • With WhenInUse authorization, apps are forbidden from automatically triggering location-tracking while your app is in the background (as described above, the plugin cannot automatically transition from the "stationary" to "moving" state in the background).
  • With WhenInUse authorization, your app must manually transition to the "moving" state while your app is in the foreground by calling .changePace(true), like a "Jogging App" would do, where the user might click a [Start Workout] button before putting the phone in their pocket.
  • Config.stopTimeout will still apply: once stopTimeout expires, your app will transition to the stationary state by turning location-services off.
  • Geofencing requires Always location authorization. Geofences cannot operate with WhenInUse authorization.

iOS

On iOS devices containing the M7 Chip (iPhone 5s+), Background Geolocation will monitor the `CMMotionActivtyManager API. Use of this API requires the "Motion & Fitness" permission and the plugin is highly optimized for this API. You are strongly recommended to not disable this API -- doing so will decrease battery performance.

Only the CMMotionActivityManager API can determine the motion-activity of the device (ie: still, on_foot, running, on_bicycle, in_vehicle). The plugin uses this API to quickly toggle location-services on/off. For example, if a car stops at a red light, the still activity will cause the plugin to toggle location-services off while the car is stopped. Once the vehicle advances through the green light, the plugin will immediately turn location-services back on.

iOS "Stationary" State

iOS is far more strict than Android for apps running in the background. When an iOS app is in the background in the stationary state (ie: location-services off), iOS has suspended the app. There is no code running at all -- your app is sleeping. While in this state, the plugin has created a "stationary geofence" of stationaryRadius meters around the last known position.

In debug mode, when the plugin has successfully created the "stationary geofence" and entered the stationary state, it will emit a sound effect:

bling

When your app is moved to the background while in the stationary state, iOS will suspend your app. Your app is completely asleep and no code is running.

iOS will re-awaken your app only when the device exits this "stationary geofence". NOTE: Exiting the stationary-geofence typically requires ~200 meters of movement. Even if you configure a stationaryRadius: 25, iOS will still require the device to move ~200 meters.

iOS #preventSuspend mode

BackgroundGeolocation has implemented a clever #preventSuspend mode which can keep your iOS app running indefinitely in the background, preventing iOS from suspending your app while in the stationary state. While in the mode, your code will never cease running.

The plugin does not achieve the preventSuspend behaviour using any black magic or unacceptable means. All that's required is Apple agree to grant your app the background location capability. If you're making any kind of "fleet tracking" or "exercise" app, #preventSuspend will be perfectly acceptable.

Since your app is completely awake in the background with preventSuspend, the plugin is able to constantly monitor the CMMotionActivityManager API and respond quickly to motion-activity changes, allowing the plugin to trigger a state-change to moving without requiring the usual "stationary-geofence" exit (typically 200 meters). While in preventSuspend, iOS can behave just like Android, requiring only a few meters of movement to change state to moving.

⚠️ WARNING: Since preventSuspend will keep your app running indefinitely in the background (albeit without running location-services constantly), your app will consume more power simply because your app is awake. You must take special care to actively manage this feature. You should not expect to run your app in preventSuspend indefinitely. It should be used for managed periods-of-time and turned off when no longer required. For most users, this mode is probably not required.

iOS heartbeat Event

While in the #preventSuspend mode in the stationary state, the plugin is able to fire a heartbeat event periodically (#heartbeatInterval). The heartbeat event will fire your Javascript callback:

BackgroundGeolocation.onHeartbeat', (event) => {
  console.log('- heartbeat event received', event);
});

iOS "Moving" state

When iOS detects a transition out of the "stationary geofence", the plugin will change state from stationary -> moving. The following image shows the device exiting the stationary geofence, where location-services are engaged and aggressive tracking is initiated:

Once in the moving state, location-services are on and the plugin will begin recording a location each distanceFilter meters. In the moving state, iOS has awakened your app in the background and will remain awake for as long as plugin remains in the moving state.

In debug mode, the plugin will emit a sound effect to dramatically announce stationary exit:

dee-do-dee-do...dee-do-dee-do

Distance-based Tracking

The iOS CLLocationManager API is strictly distance-based. Thus, it does not make sense to think in terms of time. For example, it's not a question of "I want a location every minute" -- the question is "I want a location every 100 meters". If you configure a distanceFilter: 100 and stand in the same location for 5 minutes, the iOS CLLocationManager API will not return a location. This makes complete sense, since the device battery is a precious, finite resource.

Android

Android devices monitor the ActivityRecognitionAPI from Google Play Services. Since the Android platform is much less strict with background-operation than iOS, Android does not require the use of a "stationary geofence" to determine when the device is moving. Instead, Android is able to monitor the ActivityRecognitionAPI constantly and rapidly respond to changes in device movement, typically requiring less than ten meters of movement to change state from stationary -> moving.

Android "Stationary" State

In the stationary-state, Android is constantly listening to the ActivityRecognitionAPI for changes in motion-activity. Android can suspend your WebView (where your Javascript lives) and even delay reporting of motion-activity updates, in spite of your configured #activityRecognitionInterval, if the device remains still for long periods of time. However, if the ActivityRecognitionAPI detects a change in motion-activity, it will reawaken your app to respond to that change.

Android heartbeat Event

While in the stationary state, since Android does not completely suspend apps in the background, the plugin is able to fire a heartbeat event periodically (#heartbeatInterval). The heartbeat event is implemented using the Android AlarmManager mechanism and is guaranteed to fire your Javascript callback:

BackgroundGeolocation.onHeartbeat', (event) => {
  console.log('- heartbeat event received', event);
});

The heartbeat event will cease once the plugin enters the moving state.

Android "Moving" State

When the plugin detects a motion-activity of on_foot, running, on_bicycle or in_vehicle, it will immediately change state to moving. Location-services will be engaged and the plugin will begin tracking according to your configured distanceFilter or locationUpdateInterval.

While in the moving state, if the ActivityRecognitionAPI reports a motion-activity of still, the plugin will engage the "stop-detection" system, initiating a timer of stopTimeout minutes. If the stopTimeout timer expires, the plugin will enter the stationary state. If a "moving"-type motion-activity is detected during while the stopTimeout timer is running, the timer will be cleared and the plugin will remain in the moving state.

Distance-based Tracking

Unlike iOS, Android allows both distance and time-based tracking. Like iOS, engage distance-based tracking simply by providing a distanceFilter > 0 (eg: distanceFilter: 50)

Time-based Tracking

To engage time-based tracking on Android, simply configure distanceFilter: 0. The plugin will record a location each locationUpdateInterval milliseconds (eg: locationUpdateInterval: 30000 will record a location every 30s.