Tracing memory leaks around RecyclerView using LeakCanary

Tram Ho

Overview

This article is primarily intended for programmers who are new to Android who haven’t really dig into LeakCanary yet. I myself have used it for the first time recently after researching Android development. And I’m amazed at the power of this tool. This is definitely a tool not to be missed in every project. At the same time, I am very surprised at how Android maintains the references for RecyclerViews. With the expectation that RecyclerView itself should avoid circular references, you could easily fall into the memory leak trap. (And that’s exactly why Square guys implement LeakCanary and everyone should use it.)

How to use LeakCanary

How to use LeakCanary is quite simple. As explained in the README section, you just need to 1. describe the dependency in gradle and 2. write a few lines of code in the sub class of your Application. And then LeakCanary will warn you about memory leak issue in your debug build.

However, there is a pitfall I have caught. If you are like me and want to press the Debug button instead of the Run button on Android Studio, then LeakCanary won’t run while you are debugging. You have to stop debugging and start the installed debug build from the launcher. (Run debug mode)

A case you can easily produce memory leak

One scenario where you can easily create a memory leak is: Consider one where I was quite surprised when it caused a memory leak. The basic structure of the code looks like this:

Application Structure

The Fragment displays the RecyclerView and its adapter provides the Viewholder custome. Another thing that is different from the simplest structure is that the Fragment keeps the adapter reference. This reference is meant to reuse the adapter after Activity is refreshed due to screen rotation, etc.We are displaying a RecyclerView at the top of Fragment, so I think it’s a reasonable option for round matching The life of the RecyclerView adapter with one of the Fragments is the opposite of the Activity.

The code example would look like this:

The above code looks safe as it doesn’t seem to have any circular references. However, my guess is wrong. Object reference path, the object reference path provided by LeakCanary looks like this:

Through this diagram tells us that RecyclerView.mAdapter holds an indirect reference to MainActivity via RecyclerView.mContext. This is not a reference that we create by ourselves. This is a “hidden” reference, if we can call it. So the actual structure with this “hidden” reference (denoted by dashed lines) is like the next diagram below:

You can see there is a nice circular reference from MainFragment => MainRecyclerViewAdapter => RecyclerView => MainActivity => MainFragment, etc.Screen rotation event occurs and MainActivity is recreated, but because MainFragment persists after rotation screen and keep indirect reference to old MainActivity, old MainActivity will never be recovered by GC and leak memory.

On a side note, the RecyclerView is always recreated after rotating and referencing from MainFragment to the old RecyclerView via the Android-Kotlin extension never stays after rotating (indicated by a red cross in the diagram). . That’s how Android works.

Solution 1

One simple solution is to shorten the lifetime of the adapter to match the Activity. The example code would change as follows:

Every time the screen rotation occurs, you will get rid of the old adapter that holds an indirect reference to the old Activity.

If we look at the structure, we no longer refer to the circle since we removed the link from Fragment to the adapter.

The downside to this approach, however, is that you cannot save the temporary state in the adapter, because the adapter is initialized every time the screen is rotated. We must store the temporary state somewhere else and let the adapter fetch the state after each initialization.

Solution 2

Another simple solution is to call RecyclerView.adapter = null from onDestroyView.

To be honest, I was very surprised when this way worked. Even if you cancel the reference from RecyclerView to the adapter, as long as the adapter has a reference to RecyclerView, you will still have a circular reference. The only way that I can understand is that Android actually disables the reference from the adapter to the RecyclerView as well as when you cancel the reverse reference, thus removing the circular reference completely.

Summary

In any case, you want to prepare your mental model to include “hidden” references, to handle such memory leaks flexibly. And LeakCanary can really help shape this mental model. Otherwise we wouldn’t know that there are such hidden references around RecyclerView without reading the code.

Another interesting point that I want to note is that this type of memory leak (leak memory) does not happen with ViewPager. Your fragment can keep reference to ViewPager.adapter and it doesn’t leak memory. The way ViewPager places “hidden” references will be a little different from how RecyclerView does it.

Share the news now

Source : Viblo