I went through the same issue. And after three days of trying things out, I got to work on it out.
I also had to collect a location in a foreground state like you, and if the foreground service was destroyed, I had to unregister.
The first mistake I made was not to guarantee that removeLocationUpdates
would be run on the same thread as the requestLocationUpdates
. Actually, it doesn't have to be the same thread, but after a requestLocationUpdates
, you must call removeLocationUpdates
to make the next requestLocationUpdates
valid. To ensure this, it is much easier to work on the same thread.
For example:
private fun FusedLocationProviderClient.requestLocation(
request: LocationRequest
): Single<LocationResult> {
return Single.create<LocationResult> { emitter ->
requestLocationUpdates(request, object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
removeLocationUpdates(object : LocationCallback() {})
.addOnCompleteListener {
if (emitter.isDisposed) {
info("onLocationResult called after disposing.")
return@addOnCompleteListener
}
if (result != null && result.locations.isNotEmpty()) {
onSuccess(result)
} else {
onError(RuntimeException("Invalid location result"))
}
}
}
private fun onError(error: Exception) {
if (!emitter.isDisposed) {
emitter.onError(error)
}
}
private fun onSuccess(item: LocationResult) {
if (!emitter.isDisposed) {
emitter.onSuccess(item)
}
}
}, Looper.getMainLooper())
}
}
As the code suggests, I have attracted Single's emitter
to the addOnCompleteListener
in removeLocationUpdates
to ensure the call of removeLocationUpdates
behind the requestLocationUpdates
. Without RxJava, of course, it would be easier to implement.
The second mistake I made was the wrong interval
setting in LocationRequest
. According to the doc:
This method sets the rate in milliseconds at which your app prefers to receive location updates. Note that the location updates may be somewhat faster or slower than this rate to optimize for battery usage, or there may be no updates at all (if the device has no connectivity, for example).
The explanation is unkind but ultimately, if you call requestLocationUpdates
once, you must have a Location update event triggered by interval
before the next requestLocationUpdates
. Finding this bug was the hardest.
The third mistake I made was to set the wrong priority
in LocationRequest. In API 10 and below, it was not PRIORITY_BALANCED_POWER_ACCURACY
but it was resolved by using PRIORITY_HIGH_ACCURACY
. In this case, I have only tested on the emulator, so the actual device may have different results. I guess PRIORITY_BALANCED_POWER_ACCURACY
doesn't seem to work properly because the emulator doesn't provide Bluetooth hardware.
So my LocationRequest
looks like:
LocationRequest.apply {
priority = PRIORITY_HIGH_ACCURACY
interval = 10000L
}
I hope the three mistakes and solutions I made is helpful to you!