Background location stopped when the screen turned off on Android 14: foreground service types
Our delivery app tracks a driver's route during a delivery. After targeting Android 14, tracking stopped minutes after the screen turned off, and on some devices the app crashed when tracking started. Android 14 requires a declared foreground service type and permission. How we fixed it.
Our delivery app records the driver's route while a delivery is in progress, so customers can see the
driver approaching and support can resolve disputes. After a release that raised targetSdkVersion to 34
(Android 14), the operations team reported routes with large gaps: the dot froze on the map a few minutes
after drivers locked their phones and jumped ahead when they unlocked them.
A smaller number of drivers reported something worse: the app crashed the moment they tapped "Start delivery".
The crash
From a crash report for an Android 14 device:
android.app.MissingForegroundServiceTypeException: Starting FGS without a type
callerApp=ProcessRecord{...com.example.delivery} targetSDK=34
at android.app.MissingForegroundServiceTypeException$1.createFromParcel(...)
at android.app.ActivityManager.startForegroundService(...)
at com.example.delivery.tracking.TrackingService.onStartCommand(TrackingService.kt:44)
Our tracking module (a small native module plus a location library) started a foreground service to keep location updates running with the screen off. On Android 14, with target SDK 34, starting a foreground service without declaring its type throws.
The gaps
On devices where the service did start (because the library's own service declared a type, but our manifest merge had altered it), we saw the other symptom: location stopped arriving after a few minutes. Checking the service state with the screen off:
adb shell dumpsys activity services com.example.delivery | grep -E "isForeground|foregroundServiceType"
isForeground=false foregroundId=0 foregroundNoti=null
The service had been started but never promoted to foreground. Without a foreground service, Android restricts background location access heavily, and app processes in the background are eligible to be stopped.
What we ruled out
- Location permission denied. The app had
ACCESS_FINE_LOCATION, and drivers had granted it. For a foreground service started while the app is visible, background location permission is not required. - GPS hardware or signal. Unlocking the phone resumed updates instantly.
- A JavaScript timer problem. The JS code only received updates; the gap existed in the native location callbacks too.
Root cause
Since Android 14, apps targeting API 34 must declare a type for every foreground service in the manifest
and hold the matching permission, such as FOREGROUND_SERVICE_LOCATION for location. Our service was
declared without android:foregroundServiceType="location", and the type-specific permission was missing,
so starting it as a foreground service failed (crash) or the start path that caught the error left it in
the background (gaps).
Foreground services on recent Android versions
| Version | Change relevant to location tracking |
|---|---|
| Android 10 | Background location requires ACCESS_BACKGROUND_LOCATION; foreground services need foregroundServiceType="location" to access location |
| Android 12 | Apps cannot start foreground services from the background, with exceptions |
| Android 14 (target 34) | Every foreground service must declare a type and hold a type-specific permission |
A foreground service shows a persistent notification and tells the system the user is aware of ongoing work. In return, the app keeps running and can access location while the screen is off.
Starting the service correctly
The service must call startForeground() with the type shortly after being started, and on Android 14 the
app must be in a state where starting a location foreground service is allowed: typically, the user just
tapped a button in the visible app, and the app holds location permission.
Who is affected
Any app that raised targetSdkVersion to 34 or later and starts foreground services through native code
or a library that has not been updated. React Native location and tracking libraries released before
Android 14 support, and custom services in the app's own Android code, are the usual sources.
Environment
| Component | Version / value |
|---|---|
| React Native | 0.79, New Architecture |
| Target SDK | 34 (raised from 33) |
| Tracking | Custom native module with a Service, fused location provider |
| Devices | Android 14 and 15 crashed or stopped; older versions worked |
Manifest: declare type and permissions
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service
android:name=".tracking.TrackingService"
android:exported="false"
android:foregroundServiceType="location" />
</application>
</manifest>
POST_NOTIFICATIONS (Android 13 and later) is needed so the foreground service notification is visible;
the app asks for it before the first delivery.
Service: start in the foreground with the type
class TrackingService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Delivery in progress")
.setContentText("Sharing your location with the customer")
.setSmallIcon(R.drawable.ic_route)
.setOngoing(true)
.build()
ServiceCompat.startForeground(
this, NOTIFICATION_ID, notification,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION else 0
)
startLocationUpdates()
return START_STICKY
}
}
ServiceCompat.startForeground handles the API differences. The service starts when the driver taps
"Start delivery", while the app is in the foreground, which satisfies the start restrictions.
Location request tuned for battery
val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10_000L)
.setMinUpdateDistanceMeters(15f)
.setWaitForAccurateLocation(false)
.build()
fusedClient.requestLocationUpdates(request, callback, Looper.getMainLooper())
Ten seconds and 15 metres were enough for the customer map and cut battery use compared with our previous two-second interval.
Manufacturer battery optimisation
Some manufacturers add aggressive background restrictions beyond stock Android. For drivers, who use company-managed devices, the device management profile excludes the app from battery optimisation. For personal devices, the app links to the system's battery optimisation settings with an explanation, rather than requesting the exemption silently.
React Native side
The JavaScript only starts and stops tracking through the native module and displays updates; it does not need to run while the screen is off, because the native service records points to local storage and uploads them in batches.
Alternatives and trade-offs
- Updated third-party tracking libraries that declare service types correctly. We evaluated two; either would have worked, but our custom module was small enough to fix.
- Background location permission with geofencing or periodic work instead of a foreground service. Unsuitable for continuous route tracking and harder to justify in Play Store review.
How to test the fix
adb shell dumpsys activity services com.example.delivery | grep -E "isForeground|types="
# isForeground=true ... types=0x00000008 (location)
adb shell input keyevent KEYCODE_POWER # screen off
sleep 600
adb logcat -d | grep TrackingService | tail -3 # updates still arriving
We also ran a two-hour drive test on Android 14 and 15 devices from three manufacturers with the screen off.
Confirming in production
After the release, route gaps longer than 60 seconds dropped from about 30% of deliveries on Android 14 and
later to under 2%, mostly in tunnels. The MissingForegroundServiceTypeException crash disappeared from
crash reports.
Regression guard
- A CI check parses the merged manifest of release builds and fails if any
<service>that callsstartForegroundlacksforegroundServiceType. - Raising
targetSdkVersionis a separate pull request with a checklist from the Android behaviour changes page.
Security implications
Continuous location is sensitive personal data. The persistent notification makes tracking visible to the driver, tracking runs only during an active delivery, and the Play Store data safety form and in-app explanation describe exactly what is collected and why. Stored route points are deleted after 30 days.
Prevention checklist
- Declare
foregroundServiceTypefor every foreground service when targeting Android 14. - Add the matching
FOREGROUND_SERVICE_*permission. - Start location foreground services from a visible user action.
- Request notification permission so the ongoing notification is shown.
- Test tracking with the screen off on recent Android versions before raising the target SDK.
References
- Android Developers, Foreground service types: https://developer.android.com/develop/background-work/services/fg-service-types
- Android 14 behaviour changes, foreground service types required: https://developer.android.com/about/versions/14/changes/fgs-types-required
- Android Developers, Access location in the background: https://developer.android.com/develop/sensors-and-location/location/background
- Android Developers, Restrictions on starting a foreground service from the background: https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start
Add a label
Embed this post
<iframe src="https://www.hibug.me/react-native-background-location-stops-android-14" width="700" height="420" frameborder="0" loading="lazy"></iframe>
I have spent most of the last decade on mobile teams, first native iOS, then React Native and Flutter for companies that needed one team to ship to both stores. The work I enjoy most is the unglamorous part between "it runs on my phone" and "it is live for a million users": code signing, release builds, store review, crash triage and background behaviour that differs on every Android vendor. Most of the bugs I write about here cost a release day. Each post shows the exact error, what we ruled out, the fix and how we proved it in the next build, so you can get your own release out the door sooner.
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.