mapbox / mapbox/mapbox-navigation-android
NavigationView leaks listeners attached via NavigationViewOptions
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 651
- Forks
- 321
- PR merge metrics
- No merged PRs in 30d
Description
Steps to trigger behavior
- Run any activity that attaches listeners that contain context reference via
NavigationViewOptions. Reproducible withNavigationViewActivityexample. - Rotate the screen.
- Leak.
┬───
│ GC Root: System class
│
├─ android.view.inputmethod.InputMethodManager class
│ Leaking: NO (InputMethodManager↓ is not leaking and a class is never leaking)
│ ↓ static InputMethodManager.sInstance
├─ android.view.inputmethod.InputMethodManager instance
│ Leaking: NO (ViewRootImpl↓ is not leaking and InputMethodManager is a singleton)
│ ↓ InputMethodManager.mCurRootView
├─ android.view.ViewRootImpl instance
│ Leaking: NO (MapboxGLSurfaceView↓ is not leaking and ViewRootImpl#mView is not null)
│ ↓ ViewRootImpl.mSurfaceChangedCallbacks
├─ java.util.ArrayList instance
│ Leaking: NO (MapboxGLSurfaceView↓ is not leaking)
│ ↓ ArrayList.elementData
├─ java.lang.Object[] array
│ Leaking: NO (MapboxGLSurfaceView↓ is not leaking)
│ ↓ Object[].[0]
├─ com.mapbox.mapboxsdk.maps.renderer.glsurfaceview.MapboxGLSurfaceView instance
│ Leaking: NO (MapView↓ is not leaking and View attached)
│ mContext instance of com.mapbox.navigation.examples.ui.NavigationViewActivity with mDestroyed = false
│ View.parent com.mapbox.mapboxsdk.maps.MapView attached as well
│ View#mParent is set
│ View#mAttachInfo is not null (view attached)
│ View.mWindowAttachCount = 1
│ ↓ MapboxGLSurfaceView.mParent
├─ com.mapbox.mapboxsdk.maps.MapView instance
│ Leaking: NO (CoordinatorLayout↓ is not leaking and View attached)
│ mContext instance of com.mapbox.navigation.examples.ui.NavigationViewActivity with mDestroyed = false
│ View.parent androidx.coordinatorlayout.widget.CoordinatorLayout attached as well
│ View#mParent is set
│ View#mAttachInfo is not null (view attached)
│ View.mID = R.id.navigationMapView
│ View.mWindowAttachCount = 1
│ ↓ MapView.mParent
├─ androidx.coordinatorlayout.widget.CoordinatorLayout instance
│ Leaking: NO (NavigationView↓ is not leaking and View attached)
│ mContext instance of com.mapbox.navigation.examples.ui.NavigationViewActivity with mDestroyed = false
│ View.parent com.mapbox.navigation.ui.NavigationView attached as well
│ View#mParent is set
│ View#mAttachInfo is not null (view attached)
│ View.mID = R.id.navigationLayout
│ View.mWindowAttachCount = 1
│ ↓ CoordinatorLayout.mParent
├─ com.mapbox.navigation.ui.NavigationView instance
│ Leaking: NO (View attached)
│ mContext instance of com.mapbox.navigation.examples.ui.NavigationViewActivity with mDestroyed = false
│ View.parent androidx.constraintlayout.widget.ConstraintLayout attached as well
│ View#mParent is set
│ View#mAttachInfo is not null (view attached)
│ View.mID = R.id.navigationView
│ View.mWindowAttachCount = 1
│ ↓ NavigationView.navigationViewEventDispatcher
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ com.mapbox.navigation.ui.NavigationViewEventDispatcher instance
│ Leaking: UNKNOWN
│ ↓ NavigationViewEventDispatcher.bannerInstructionsListener
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~
╰→ com.mapbox.navigation.examples.ui.NavigationViewActivity instance
Leaking: YES (ObjectWatcher was watching this because com.mapbox.navigation.examples.ui.NavigationViewActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
key = f8ed9703-1913-4718-8392-50b4120e0c70
watchDurationMillis = 24175
retainedDurationMillis = 19174
METADATA
Build.VERSION.SDK_INT: 30
Build.MANUFACTURER: Google
LeakCanary version: 2.3
App process name: com.mapbox.navigation.examples
Analysis duration: 15309 ms
The issue is, that we're storing references to the listeners (and context that they carry) in the view model. As far as I can tell from a glance at the codebase, we cannot work around the issue as a user. The only way to subscribe to most of the NavigationView listeners is through the options. If we opt to wrap our listeners in a weak reference or anything that avoids carrying the context, then we would lose that reference after rotation and wouldn't be able to re-establish it.
We could try resolving the issue by deprecating subscribing to events via options and expose register/unregister methods instead so that users can manage the state and avoid leaking.
/cc @abhishek1508 @JunDai @cafesilencio
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with NavigationViewActivity and the NavigationViewOptions listener setup, then trace NavigationViewEventDispatcher and the view model that stores those listeners. Reproduce the rotation scenario and inspect the retained-reference path. Done means the listener subscription approach no longer retains a destroyed Activity, with the proposed registration lifecycle clear for callers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100