microg/GmsCore · error · RuntimeException

Binder died

Error message

Binder died

What it means

Before delivering a location update, LocationRequestManager.check() verifies that the client's callback Binder (ILocationListener) is still alive. If the client process died or unbound without removing its listener, callback.asBinder().isBinderAlive is false and a RuntimeException 'Binder died' is thrown instead of delivering to a dead endpoint.

Source

Thrown at play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt:459

            fun start(): LocationRequestHolder {
                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException("Lack of permission")
                return this
            }

            fun cancel() {
                try {
                    callback?.cancel()
                } catch (e: Exception) {
                    Log.w(TAG, e)
                }
            }

            fun check() {
                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException("Lack of permission")
                if (effectiveGranularity > permissionGranularity) throw RuntimeException("Lack of permission")
                if (timePendingMillis < 0) throw RuntimeException("duration limit reached (active for ${(SystemClock.elapsedRealtime() - start).formatDuration()}, duration ${request.durationMillis.formatDuration()})")
                if (updatesPending <= 0) throw RuntimeException("max updates reached")
                if (callback?.asBinder()?.isBinderAlive == false) throw RuntimeException("Binder died")
            }

            fun processNewLocation(location: Location): Boolean {
                check()
                if (lastLocation != null && location.elapsedMillis - lastLocation!!.elapsedMillis < request.minUpdateIntervalMillis) return false
                if (lastLocation != null && location.distanceTo(lastLocation!!) < request.minUpdateDistanceMeters) return false
                if (lastLocation == location) return false
                val returnedLocation = if (effectiveGranularity > permissionGranularity) {
                    throw RuntimeException("Lack of permission")
                } else {
                    if (!context.noteAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) {
                        throw RuntimeException("app op denied")
                    } else if (clientIdentity.isSelfProcess()) {
                        Location(location)
                    } else {
                        Location(location).apply { provider = "fused" }
                    }
                }

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Always call FusedLocationProviderClient.removeLocationUpdates() in onDestroy/onStop or when the client is torn down
  2. Re-register the location request after process restart instead of relying on the old registration
  3. Ensure the client process is alive (foreground service / proper lifecycle) for the duration of the request
  4. If you control the manager side, treat isBinderAlive==false as a signal to unregister the request rather than propagate the exception

Example fix

// before
override fun onStart() {
    client.requestLocationUpdates(request, listener, looper)
}
// after
override fun onStart() {
    client.requestLocationUpdates(request, listener, looper)
}
override fun onStop() {
    client.removeLocationUpdates(listener) // avoids orphaned request on process death
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    client.requestLocationUpdates(request, listener, looper)
} catch (e: RuntimeException) {
    if (e.message == "Binder died") {
        // re-register on the new process lifecycle
        registerOnNextStart()
    } else throw e
}

Prevention

When it happens

Trigger: The client app process was killed (or crashed) while a location request was still registered; the request was not removed via removeLocationUpdates before the process exited; the next location arrival triggers processNewLocation() -> check().

Common situations: Backgrounded apps killed by the OS while holding an active location request; crashes during testing leaving orphaned requests; a client that unbinds its service without cleaning up listeners; debugging with 'Don't keep activities' enabled.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/782b57cd30931a50. Report an issue: GitHub.