One thing that deviates from simplest structure is that the Fragment keeps reference to the adapter.
This reference is meant to reuse adaptereven after Activity is refreshed due to rotation etc.
We are showing RecyclerView on top of the Fragment, so I think it is a sensible option to match the lifetime of RecyclerView's adapter to the one of the Fragmentas opposed to the Activity.
The corresponding part of the code looks as follows :This structure looks memory leak safe because there doesn’t seem any circular references.
However, my naiive expectation is false.
The object reference path provided by LeakCanary looks like this:Object reference path by LeakCanaryTo my surprise, this diagram tells me that RecyclerView.
mAdapter holds an indirect reference to MainActivity through RecyclerView.
This is not a reference we made ourselves.
This is a “hidden” reference, if we may call it.
So, the actual structure with this “hidden” reference (indicated by the dashed lines) is something like the next diagram.
Actual App StructureYou can see there is a beautiful circular reference from MainFragment => MainRecyclerViewAdapter => RecyclerView=> MainActivity => MainFragment and so on.
Rotation happens, and MainActivity gets recreated, but since MainFragment still lives after rotation and keeps indirect reference to the old MainActivity, the old MainActivity will never reclaimed by GC and leaks.
As a side note, the RecyclerView is always recreated after rotation and reference from MainFragment to the old RecyclerView through Android-Kotlin extension never stays after rotation (indicated by the red cross in the diagram).
That's how Android works.
Solution 1A simple solution is to shorten the lifetime of adapter to match with the one of the Activity.
Showing only the diff of before-after in the sample code below.
Every time when rotation happens, you will ditch the oldadapter that holds an indirect reference to the old Activity.
If we look at the structure, we don’t have the circular reference anymore, because we removed link from Fragment to adapter.
Structure without adapter referenceThe cons of this approach is that you cannot save the temporary state in the adapter, because the adapter is initialized at every rotation.
We have to save the temporary state somewhere else, and let the adapter to fetch the state after every initialization.
Solution 2Another simple solution is to call recyclerView.
adapter = null from onDestroyView.
Actually, I was surprised that this approach works.
Even if you null out the reference from RecyclerView to adapter, as long as the adapter has a reference to RecyclerView, you still have circular reference.
The only way I can comprehend is that Android actually nulls out the reference from adapter to the RecyclerView as well when you null out the reverse reference, thereby eliminating the circular reference entirely.
Structure after nulling out adapterSummaryEven though I think solution 1 is by-the-book approach, it has a shortcoming that you can not let adapter to hold temporary status.
If you need adapter to maintain temporary status, then probably better to pick solution 2.
In any case, you want to prepare your mental model including the “hidden” references, in order to flexibly handle such memory leak situation.
And LeakCanary can really help you shaping this mental model.
Otherwise, it was impossible for me to know that there is such hidden references around RecyclerView without reading the internal code.
If you are interested, I put the sample code in GitHub.
You can follow git tags to get different stage of the code.
(adapter-memory-leak tag shows the code that causes memory leak, fix-adapter-memory-leak-1 tag shows the solution 1 to treat the memory leak, etc.
)Another interesting point I want to note is that this type of memory leak does not occur with ViewPager.
Your Fragment can hold reference to ViewPager.
adapterand it causes no memory leak.
The way the ViewPager set "hidden" references should be a bit different from how RecyclerView does.
That is it.
Bye bye memory leaks.
Long live LeakCanary!!.