I´ve just added CanaryLeak to my project to see if there are any memory leaks in my application and noticed, that there is indeed a leak in one my fragments due to a Snackbar.
I´m creating a Snackbar in the onCreateView
and set it to null
in onDestroyView
. However I get a memory leak every time I rotate the screen.
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup
container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_backend, container,
false);
Activity parentActivity = getActivity();
if (parentActivity != null) {
mConnectSnackbar =
Snackbar.make(parentActivity.findViewById(R.id.nav_host_fragment),
"Connect", Snackbar.LENGTH_INDEFINITE);
mConnectSnackbar.setAction(getString(R.string.connect), v ->
startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS)));
}
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
mConnectSnackbar.setAction("Connect", null);
mConnectSnackbar.dismiss();
mConnectSnackbar = null;
}
As I clear the reference to the action and the Snackbar itself there shouldn´t be any reason for a memory leak. However I can´t figure out, what the reason might be and the Heap Dump from Canary Leak doesn´t help either. I have a suspicion that it might me due to the reference to the nav_host_fragment, but I don´t know if it´s true and how to fix it.
Many Thanks for your help.
Added Leak Trace and removed hprof file.
┬
├─ android.view.accessibility.AccessibilityManager
│ Leaking: NO (a class is never leaking)
│ GC Root: System class
│ ↓ static AccessibilityManager.sInstance
│ ~~~~~~~~~
├─ android.view.accessibility.AccessibilityManager
│ Leaking: UNKNOWN
│ ↓ AccessibilityManager.mTouchExplorationStateChangeListeners
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ android.util.ArrayMap
│ Leaking: UNKNOWN
│ ↓ ArrayMap.mArray
│ ~~~~~~
├─ java.lang.Object[]
│ Leaking: UNKNOWN
│ ↓ array Object[].[4]
│ ~~~
├─ androidx.core.view.accessibility.AccessibilityManagerCompat$TouchExplorationStateChangeListenerWrapper
│ Leaking: UNKNOWN
│ ↓ AccessibilityManagerCompat$TouchExplorationStateChangeListenerWrapper.mListener
│ ~~~~~~~~~
├─ com.google.android.material.snackbar.BaseTransientBottomBar$SnackbarBaseLayout$1
│ Leaking: UNKNOWN
│ ↓ BaseTransientBottomBar$SnackbarBaseLayout$1.this$0
│ ~~~~~~
╰→ com.google.android.material.snackbar.Snackbar$SnackbarLayout
Leaking: YES (View.mContext references a destroyed activity)
mContext instance of android.view.ContextThemeWrapper, wrapping activity com.twaice.twaice.MainActivity with mDestroyed = true
View#mParent is null
View#mAttachInfo is null (view detached)
View.mWindowAttachCount = 0
This is a memory leak in the material-components-android library. I just filed an issue: https://github.com/material-components/material-components-android/issues/497
This leak only happens if a snackbar is created but never shown as described in the issue:
In Material Library 1.0.0, when a BaseTransientBottomBar .SnackbarBaseLayout instance is created, it registers a TouchExplorationStateChangeListener which it then unregisters onDetachedFromWindow(). If the SnackbarBaseLayout is created but never attached (which happens), then it never gets detached. When the underlying context (an activity) gets destroyed, the TouchExplorationStateChangeListener is kept in memory by AccessibilityManager, holding on to its outer class SnackbarBaseLayout which itself holds on to its context, a destroyed activity. Effectively SnackbarBaseLayout is leaking destroyed activities and the entire view hierarchy.
The good news is that this code no longer exists in the 1.1.0 releases, so the leak is gone, though unfortunately 1.1.0 is still in alpha release.
Note: in future posts, consider providing the text leaktrace that LeakCanary outputs, which is useful to solve a memory leak.