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.
At a glance
Section titled “At a glance”| 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.
What survives a kill
Section titled “What survives a kill”- 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; readgetState().lastLocationwhen the app opens.
Android
Section titled “Android”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".
Swiped away from recents
Section titled “Swiped away from recents”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.
Killed for memory, or crashed
Section titled “Killed for memory, or crashed”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.
Reboot and app update
Section titled “Reboot and app update”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.
Force stop
Section titled “Force stop”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.
The permission behind every restart
Section titled “The permission behind every restart”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.
Swiped away
Section titled “Swiped away”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.
Killed for memory, or crashed
Section titled “Killed for memory, or crashed”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.
Reboot and app update
Section titled “Reboot and app update”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.
Your launch code
Section titled “Your launch code”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();}How you’ll know
Section titled “How you’ll know”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.