-
Notifications
You must be signed in to change notification settings - Fork 276
Philosophy of Operation
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.
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);
});
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');
});
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.
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: oncestopTimeout
expires, your app will transition to the stationary state by turning location-services off. - Geofencing requires
Always
location authorization. Geofences cannot operate withWhenInUse
authorization.
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 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 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.
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.
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.
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);
});
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
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 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.
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.
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.
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.
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
)
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.