Location is one of the most battery-intensive operations on Android. Choosing the right location provider, update interval, and priority directly determines whether your app drains a user's battery in an hour or runs comfortably all day.
Location Providers
| Provider | Accuracy | Battery | Latency |
|---|---|---|---|
GPS (PRIORITY_HIGH_ACCURACY) | 3–10m | High | 30–60s cold start |
| Network + GPS fusion | 10–50m | Medium | 1–5s |
Network only (PRIORITY_BALANCED_POWER) | 100–1000m | Low | ~1s |
Passive (PRIORITY_PASSIVE) | Varies | Minimal | Whenever another app gets a fix |
Fused Location Provider
FusedLocationProviderClient is the recommended API — it automatically selects the best source:
class LocationManager(private val context: Context) {
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
// Get last known location (no GPS activation — minimal battery)
suspend fun getLastLocation(): Location? {
return if (ContextCompat.checkSelfPermission(
context, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
fusedLocationClient.lastLocation.await()
} else null
}
// Continuous updates — for navigation or active tracking
fun requestContinuousUpdates(callback: (Location) -> Unit): LocationCallback {
val request = LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY, // GPS + network fusion
5_000L // update interval: 5 seconds
)
.setMinUpdateDistanceMeters(10f) // only update if moved >10m
.setMinUpdateIntervalMillis(2_000L) // fastest possible update
.build()
val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let(callback)
}
}
fusedLocationClient.requestLocationUpdates(
request, locationCallback, Looper.getMainLooper()
)
return locationCallback
}
fun stopUpdates(callback: LocationCallback) {
fusedLocationClient.removeLocationUpdates(callback)
}
}
Strategy Selection by Use Case
fun buildLocationRequest(strategy: LocationStrategy): LocationRequest {
return when (strategy) {
// Navigation: high accuracy, frequent updates
LocationStrategy.NAVIGATION -> LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY, 1_000L // 1s interval
).setMinUpdateDistanceMeters(2f).build()
// Ride tracking (driver): medium accuracy, less frequent
LocationStrategy.RIDE_TRACKING -> LocationRequest.Builder(
Priority.PRIORITY_BALANCED_POWER, 5_000L // 5s interval
).setMinUpdateDistanceMeters(10f).build()
// Background geofencing check: passive + low accuracy
LocationStrategy.BACKGROUND -> LocationRequest.Builder(
Priority.PRIORITY_LOW_POWER, 60_000L // 1 min interval
).setMinUpdateDistanceMeters(100f).build()
// One-time city-level location
LocationStrategy.COARSE -> LocationRequest.Builder(
Priority.PRIORITY_PASSIVE, Long.MAX_VALUE
).setMaxUpdates(1).build()
}
}
enum class LocationStrategy { NAVIGATION, RIDE_TRACKING, BACKGROUND, COARSE }
Permission Handling
class LocationPermissionHelper(private val activity: ComponentActivity) {
private val permissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true ->
onFineLocationGranted()
permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true ->
onCoarseLocationGranted()
else ->
onLocationDenied()
}
}
fun requestForegroundLocation() {
permissionLauncher.launch(arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
))
}
// Background location requires explicit separate prompt and user going to settings on API 30+
fun requestBackgroundLocation() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
permissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
}
}
}
Battery Optimization Rules
// Stop updates when app goes to background (if not needed there)
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStop(owner: LifecycleOwner) {
locationManager.stopUpdates(locationCallback)
}
override fun onStart(owner: LifecycleOwner) {
locationManager.requestContinuousUpdates { location -> handleLocation(location) }
}
})
Key Takeaways
| Strategy | Battery cost | Use for |
|---|---|---|
PRIORITY_HIGH_ACCURACY | High | Active navigation, real-time tracking |
PRIORITY_BALANCED_POWER | Medium | Background periodic checks |
PRIORITY_LOW_POWER | Low | City-level background location |
lastLocation | Minimal | One-time lookup; show map center |
setMinUpdateDistanceMeters | Saves battery | Don't update if user hasn't moved |
| Stop in background | Critical | Always stop when not needed |