Leak canary, Recyclerview leaking mAdapter

First of all, I'm referencing this file.

It looks like v7.widget.RecyclerView is leaking the adapter, and not my application. But that can't be right.... right?

It's actually your adapter that's leaking the RecyclerView (and it's made pretty clear by the trace graph and the title of the LeakCanary activity). However, I'm not sure if it's the "parent" RecyclerView or the nested one in the HourlyViewHolder, or both. I think the culprits are your ViewHolders. By making them non-static inner classes, you explicitly provide them with the reference to the enclosing adapter class, ant this almost directly couples the adapter with the recycled views, since the parent of every itemView in your holders is the RecyclerView itself.

My first suggestion to fix this problem would be to decouple your ViewHolders and Adapter by making them static inner classes. That way they do not hold a reference to the adapter, so your context field will be inaccessible to them, and it's also a good thing, since context references should be passed and stored sparingly (also to avoid big memory leaks). When you need the context just to obtain the strings, do it somewhere else, for example in the adapter constructor, but do not store the context as a member. Finally, the DayForecastAdapter seems dangerous too: you pass the one, same instance of it to every HourlyViewHolder, which seems like a bug.

I think that fixing the design and decoupling these classes should get rid of this memory leak


If the adapter lives any longer than the RecyclerView does, you've got to clear the adapter reference in onDestroyView:

@Override
public void onDestroyView() {
    recyclerView.setAdapter(null);
    super.onDestroyView();
}

Otherwise the adapter is going to hold a reference to the RecyclerView which should have already gone out of memory.

Special note prior to androidx.fragment:fragment:1.3.0:

If the screen is involved in transition animations, you actually have to take this one step further and only clear the adapter when the view has become detached:

@Override
public void onDestroyView() {
    recyclerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
        @Override
        public void onViewAttachedToWindow(View v) {
            // no-op
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            recyclerView.setAdapter(null);
        }
    });
    super.onDestroyView();
}

With androidx.fragment:fragment:1.3.0 and above, onDestroyView happens after the view is detached and this extra code is no longer needed.


I was able to fix this by overriding RecyclerView. It happens because RecyclerView never unregisters itself from AdapterDataObservable.

@Override protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (getAdapter() != null) {
        setAdapter(null);
    }
}