{"record":{"id":"782b57cd30931a50","repo":"microg/GmsCore","slug":"binder-died","errorCode":null,"errorMessage":"Binder died","messagePattern":"Binder died","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt","lineNumber":459,"sourceCode":"            fun start(): LocationRequestHolder {\n                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException(\"Lack of permission\")\n                return this\n            }\n\n            fun cancel() {\n                try {\n                    callback?.cancel()\n                } catch (e: Exception) {\n                    Log.w(TAG, e)\n                }\n            }\n\n            fun check() {\n                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException(\"Lack of permission\")\n                if (effectiveGranularity > permissionGranularity) throw RuntimeException(\"Lack of permission\")\n                if (timePendingMillis < 0) throw RuntimeException(\"duration limit reached (active for ${(SystemClock.elapsedRealtime() - start).formatDuration()}, duration ${request.durationMillis.formatDuration()})\")\n                if (updatesPending <= 0) throw RuntimeException(\"max updates reached\")\n                if (callback?.asBinder()?.isBinderAlive == false) throw RuntimeException(\"Binder died\")\n            }\n\n            fun processNewLocation(location: Location): Boolean {\n                check()\n                if (lastLocation != null && location.elapsedMillis - lastLocation!!.elapsedMillis < request.minUpdateIntervalMillis) return false\n                if (lastLocation != null && location.distanceTo(lastLocation!!) < request.minUpdateDistanceMeters) return false\n                if (lastLocation == location) return false\n                val returnedLocation = if (effectiveGranularity > permissionGranularity) {\n                    throw RuntimeException(\"Lack of permission\")\n                } else {\n                    if (!context.noteAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) {\n                        throw RuntimeException(\"app op denied\")\n                    } else if (clientIdentity.isSelfProcess()) {\n                        Location(location)\n                    } else {\n                        Location(location).apply { provider = \"fused\" }\n                    }\n                }","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt#L441-L477","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","solutions":["Always call FusedLocationProviderClient.removeLocationUpdates() in onDestroy/onStop or when the client is torn down","Re-register the location request after process restart instead of relying on the old registration","Ensure the client process is alive (foreground service / proper lifecycle) for the duration of the request","If you control the manager side, treat isBinderAlive==false as a signal to unregister the request rather than propagate the exception"],"exampleFix":"// before\noverride fun onStart() {\n    client.requestLocationUpdates(request, listener, looper)\n}\n// after\noverride fun onStart() {\n    client.requestLocationUpdates(request, listener, looper)\n}\noverride fun onStop() {\n    client.removeLocationUpdates(listener) // avoids orphaned request on process death\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    client.requestLocationUpdates(request, listener, looper)\n} catch (e: RuntimeException) {\n    if (e.message == \"Binder died\") {\n        // re-register on the new process lifecycle\n        registerOnNextStart()\n    } else throw e\n}","preventionTips":["Always pair requestLocationUpdates with removeLocationUpdates in lifecycle teardown","Use a foreground service for long-running location needs so the process is not killed","Handle onServiceDisconnected / process death and re-register the listener","Avoid keeping listeners registered while backgrounded if the OS may kill the app"],"tags":["location","android","binder","ipc","process-death"],"backgroundTag":"binder-died","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}