Baseflow / Baseflow/flutter-geolocator

[Android] ANR: NmeaClient.stop() makes a synchronous Binder call (unregisterGnssNmeaCallback) on the main thread inside onLocationResult

Open
#1,795 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Dart
Stars
1.3k
Forks
803
Avg merge
8h 16m
Merged PRs (30d)
1

Description

## Summary

`getCurrentPosition()` on Android can freeze the main thread and trigger an ANR. The ANR trace shows the main thread blocked in a **synchronous Binder transaction** (`ILocationManager.unregisterGnssNmeaCallback`) that `NmeaClient.stop()` issues from inside the fused location result callback, while tearing down the one-shot position request.

## Versions

- `geolocator` 14.0.3 / `geolocator_android` 5.0.3 (latest on pub.dev at the time of writing)
- Flutter 3.47.0 (stable)
- Android 14 (API 34) emulator, Google APIs image; `play-services-location` 21.2.0
- App: Flutter, New Architecture not applicable (pure Flutter app)

## Steps to reproduce

1. Call `Geolocator.getCurrentPosition()` periodically from a foreground screen (in our case, once per chat message sent, with the GPS position changing between calls — `adb emu geo fix` on the emulator).
2. Keep interacting with the UI.
3. Intermittently the app stops responding and Android reports `Input dispatching timed out ... MainActivity is not responding. Waited 5001ms for MotionEvent`.

It is not deterministic: it depends on the system location service being slow to answer the Binder call (the emulator under load makes it more likely). We could not reproduce it on demand, but we captured the full `/data/anr/` trace when it happened.

## ANR trace (main thread, from `/data/anr/anr_2026-08-29-04-51-30-750`)

```
Subject: Input dispatching timed out (br.agr.allure.allureagroapp/...MainActivity (server) is not responding. Waited 5001ms for MotionEvent ...)

"main" prio=5 tid=1 Native
native: #00 libc.so (__ioctl+7)
native: #01 libc.so (ioctl+216)
native: #02 libbinder.so (android::IPCThreadState::transact+1272)
native: #03 libbinder.so (android::BpBinder::transact+171)
native: #04 libandroid_runtime.so (android_os_BinderProxy_transact+205)
...
android.location.ILocationManager$Stub$Proxy.unregisterGnssNmeaCallback
android.location.LocationManager$GnssNmeaTransportManager.unregisterTransport
com.android.internal.listeners.ListenerTransportManager.removeListener
android.location.LocationManager.removeNmeaListener
com.baseflow.geolocator.location.NmeaClient.stop
com.baseflow.geolocator.location.FusedLocationClient.stopPositionUpdates
com.baseflow.geolocator.location.GeolocationManager.stopPositionUpdates
com.baseflow.geolocator.MethodCallHandlerImpl.lambda$onGetCurrentPosition$0
com.baseflow.geolocator.MethodCallHandlerImpl$$ExternalSyntheticLambda0.onPositionChanged
com.baseflow.geolocator.location.FusedLocationClient$1.onLocationResult
com.google.android.gms.internal.location.zzds.notifyListener
com.google.android.gms.common.api.internal.ListenerHolder.zaa
com.google.android.gms.common.api.internal.zacb.run
(play-services-location 21.2.0 / play-services-base 18.4.0)
```

So the chain is: `onLocationResult` (delivered on the main looper) → the one-shot `getCurrentPosition` callback → `stopPositionUpdates` → `NmeaClient.stop()` → `LocationManager.removeNmeaListener` → synchronous Binder call to system_server. When system_server's location service is slow, the main thread is blocked for > 5 s and the ANR fires. No Dart frames are involved.

## Notes

- Since 4.6.0 (“Always listen to NMEA message to get GPS position fix data”) the NMEA listener is registered for every request, so there is no way from the API to avoid this path: `NmeaClient.start()` does not consult `useMSLAltitude`, `LocationOptions.parseArguments` never returns null, and `forceLocationManager: true` uses the same `NmeaClient`.
- The registration side (`addNmeaListener`) is presumably subject to the same risk.

## Suggested fix

Move `NmeaClient.stop()` / `removeNmeaListener` (and ideally the `addNmeaListener` registration) off the main thread, or post it to a background executor/handler, so the Binder round-trip to the location service cannot block input dispatching. Alternatively, register the NMEA listener only when `useMSLAltitude` is requested.

We mitigated on our side by calling `getCurrentPosition` less often (reusing a recent fix), which reduces exposure but does not remove the root cause.

Contributor guide

Open the contributing guide

Research direction

Start with the NmeaClient.stop() call and trace how FusedLocationClient.stopPositionUpdates and MethodCallHandlerImpl.onGetCurrentPosition are reached from FusedLocationClient.onLocationResult. Reproduce repeated getCurrentPosition calls on Android with a slow or loaded location service, then verify that NMEA listener teardown no longer blocks the main looper long enough to trigger an ANR.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, dart, flutter, java
Domain
mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.