Search code examples
androidleakcanary

Android LocationManager leak memory


I got LocationManager instance by getSystemSercice(LOCATION_SERVICE) of activity, After a few minutes leak canary detect memory leaks:

┬───
│ GC Root: Global variable in native code
│
├─ android.location.LocationManager$ListenerTransport instance
│    Leaking: UNKNOWN
│    ↓ LocationManager$ListenerTransport.this$0
│                                        ~~~~~~
├─ android.location.LocationManager instance
│    Leaking: UNKNOWN
│    ↓ LocationManager.mContext
│                      ~~~~~~~~
├─ android.app.ContextImpl instance
│    Leaking: UNKNOWN
│    ↓ ContextImpl.mAutofillClient
│                  ~~~~~~~~~~~~~~~
╰→ com....manager.MapsActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com...manager.live2.MapsActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
​     key = 3e8186a7-b057-4c0a-aca2-b0fc4257bb11
​     watchDurationMillis = 107841
​     retainedDurationMillis = 102829

METADATA

Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: Xiaomi
LeakCanary version: 2.3
App process name: com...
Analysis duration: 9763 ms```

Solution

  • My earlier answer was incorrect, as I was assuming ContextImpl had a different lifecycle as the activity that has it as a base context. Turns out they have the same lifecycle (comment here: https://issuetracker.google.com/issues/159308651#comment6)

    What that means for this particular issue is that the leak is elsewhere. Reading through the source and leak trace:

    • LocationManager has an mContext field that points to a ContextImpl that references an activity. That means LocationManager's lifecycle should be the same as the one of the activity.
    • However, it's held in memory by LocationManager$ListenerTransport which is a binder held by native memory, used to allow another process to call back into this process. Until the other process let go of the reference to the binder on its side, the binder can't be GCed on the app side. This is a classic AOSP leak, and the fix should be either for LocationManager to an app context, or for LocationManager$ListenerTransport to let go of its ref to its outer class (ie make it a static class with nullable ref) when the activity is destroyed.

    That's all in AOSP though. In your app, can you try calling getSystemSercice(LOCATION_SERVICE) on the app context instead of the activity context? ie in your activity instead of this.getSystemSercice(LOCATION_SERVICE); try this.getApplication().getSystemSercice(LOCATION_SERVICE);.

    Edit: or it could be that the activity code isn't calling locationManager.removeUpdates(this);