Skip to content
Support the creatorSupport

Track location when the app is killed

“The app was killed” can mean six different things, and Android and iOS handle each one differently. Some never stop tracking, some come back only after the phone moves, and one only when the user opens the app.

One rule holds everywhere: after a kill, no JavaScript runs until something launches the app again, so the tracking job has to live in native code. Long Ping saves whether tracking is enabled and resumes it natively.

What happened Android iOS
Swiped away Keeps running Relaunched once the phone has moved a few hundred meters
Killed for memory, or a crash Restarted by the system, or by the 15-minute check Relaunched on movement
Reboot Restarted at boot (startOnBoot) Relaunched on movement
App update Restarted after the install (startOnBoot) Resumes at the next launch
Force stop in Settings Off until the user opens the app Not on iOS

Every automatic restart in this table needs background permission: “Always” on iOS, “Allow all the time” on Android 11 and later.

  • Tracking state: on or off, moving or parked, and the interval clock. The clock resumes from the newest stored record, so the first fix after a kill isn’t recorded early.
  • The queue: recorded fixes wait in a SQLite database that survives kills, reboots and app updates, and the native uploader keeps sending them.
  • Not the events: nothing is buffered for JavaScript. Fixes recorded while your JS wasn’t running are uploaded, not replayed to onLocation; read getState().lastLocation when the app opens.

While tracking is on, Long Ping runs a foreground service of type location, with its notification, which Android is far less likely to kill than an ordinary background app. The service is sticky (START_STICKY) and declared with stopWithTask="false".

The service keeps running, and the log says “app removed from recents; tracking continues”. With stopOnTerminate: true, a swipe-away stops tracking instead, as stop() would. Some manufacturers’ battery managers kill the process on a swipe anyway; that is then handled like the next case.

Android recreates sticky services when it can. Long Ping logs “service restarted by the system after its process was killed” and carries on from the saved state. Android 12 and later may refuse to start a foreground service from the background; Long Ping logs that, and a WorkManager watchdog that runs every 15 minutes while tracking is on starts the service again.

A boot receiver listens for BOOT_COMPLETED and MY_PACKAGE_REPLACED. If tracking was on and startOnBoot is true (the default), it starts the service without the app being opened. On a phone with a secure lock screen, Android sends BOOT_COMPLETED only after the first unlock. With startOnBoot: false, tracking stays off until the user opens the app, and the 15-minute check leaves it off too.

Opening the app is enough: tracking is still marked as enabled, so Long Ping resumes without a start() call. Some manufacturers’ battery managers stop apps in much the same way; see Android battery managers.

On Android 11 and later, a location service started while the app isn’t on screen only gets location with “Allow all the time”. Without it, Long Ping doesn’t attempt the start: it logs the reason, emits an error event with PERMISSION_DENIED, and resumes when the app is next opened. See Permissions for asking for it.

iOS has no foreground service: a tracking app stays alive because its location updates are running, and a termination ends them. Two kinds of monitoring relaunch a terminated app in the background, both only with “Always” permission. Long Ping keeps significant-change monitoring on for as long as tracking is enabled, and while parked it monitors a region around the parked position (radius max(100, stopRadiusM × 2) meters, exit only). At every launch, background relaunches included, native code resumes tracking before your JavaScript loads, if tracking was enabled and permission allows it.

iOS stops the app and its location updates. With the default stopOnTerminate: false, Long Ping makes sure both kinds of monitoring are on as the app terminates, and if the device was moving, puts a region around the last position so the relaunch comes sooner. Fixes are lost until the device has moved a few hundred meters. With stopOnTerminate: true, a swipe-away stops tracking.

iOS doesn’t always announce a termination, and a crash runs no cleanup code, so Long Ping doesn’t rely on a warning: the monitoring is already running. A parked device comes back on leaving its region, a moving one through significant-change monitoring.

After a reboot the app comes back on movement. If that happens before the first unlock, when the app’s saved state can’t be read yet, Long Ping waits for the unlock. An update has no special path on iOS: tracking resumes at the next launch, whether iOS relaunches the app or the user opens it.

Call configure() on every launch; it never starts, stops or prompts. Call start() when the user goes on duty, not at every launch: if tracking was on, it is already running again.

import LongPing, { AuthorizationStatus } from 'react-native-long-ping';
await LongPing.configure({
stopOnTerminate: false, // default: a swipe-away doesn't stop tracking
startOnBoot: true, // default: Android resumes after a reboot or app update
http: { url: 'https://api.example.com/locations' },
});
const { enabled } = await LongPing.getState();
const { status } = await LongPing.getProviderState();
if (enabled && status !== AuthorizationStatus.ALWAYS) {
// Restarts after a kill will wait for the user. Explain why, then:
await LongPing.deviceSettings.openAppSettings();
}

On your server, a kill looks like a gap in a device’s timestamps with no stop() behind it. On the device, logger.getLog() shows how tracking came back, including the restart lines quoted above. More on the iOS side in How iOS background tracking works.