Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping a reference to a View in a Fragment causes memory leaks?

Somebody told me the following, but I am a bit perplexed.

Please, would you be able to confirm or dispute it?

(the Fragment is not retained via setRetainInstance()


At the moment it is a common practice to initialize views in Fragments like this:

private lateinit var myTextView: TextView

fun onViewCreated(view: View, bundle: Bundle) {

     ...

     myTextView = view.findViewById(R.id.myTextViewId)

     ...

}

And then we never nullify this property. Though this is a common practise, it is causing a memory leak.

Background to this:

Let's say, FragmentA has a reference to a childView of it's View, as an instance field. Navigation from fragment A to B is executed by FragmentManager using a specific FragmentTransaction. Depending on the type of transaction, the Manager might want to kill the View only but still persist the instance of FragmentA(see below lifecycle part where it says "The fragment returns to the layout from the back stack"). When user navigates back from FragmentB to FragmentA, the previous instance of FragmentA will be brought to the front, but a new View will be created.

The issue is that if we keep instance to our view in the lateinit property and never clear the reference to it, the view cannot be fully destroyed, causing memory leak.

like image 536
Antony Abrams Avatar asked Sep 24 '19 12:09

Antony Abrams


People also ask

What could be the possible cause of memory leaks?

Memory leak occurs when programmers create a memory in heap and forget to delete it. The consequences of memory leak is that it reduces the performance of the computer by reducing the amount of available memory.

Do memory leaks cause permanent damage?

Physical or permanent damage does not happen from memory leaks. Memory leaks are strictly a software issue, causing performance to slow down among applications within a given system.


1 Answers

Please, is there an official answer on the matter?

An official answer regarding this matter is,

The Memory Profiler is a component in the Android Profiler that helps you identify memory leaks and memory churn that can lead to stutter, freezes, and even app crashes.

In this documentation, android officials teach you how to figure out memory leak by yourself so that they don't have to answer on each and every test cases a user may perform. Besides you can use LeakCanary which does a great job in detecting memory leak.

For your convenience, I performed a heap analysis (a similar but extended version of your use case). Before showing the analysis report I would like to give a step by step basic overview of how the memories will be de/allocated in your case,

  1. On open FragmentA: It's content/root View and the TextView will be allocated into the memory.

  2. On Navigate to FragmentB: onDestroyView() of FragmentA will be called, but FragmentA's View can not be destroyed because the TextView holds a strong reference to it and FragmentA holds a strong reference to TextView.

  3. On Navigate back to FragmentA from FragmentB: The previous allocation of the View and TextView will be cleared. At the same time, they will get new allocations as the onCreateView() is called.

  4. On Back press from FragmentA: The new allocations will be cleared as well.

Answer to your question:

In step 2, we can see there is a memory leak as the retain memory of View is not freed up what it was supposed to be. On the other hand, from step 3 we can see that the memory is recovered as soon as the user returns back to the Fragment. So we can figure out that this kind of memory leak persist till the FragmentManager brings the Fragment back.

Example / Statical Analysis

To test your case, I have created an application. My application has an Activity with a Button and a FrameLayour which is the container for fragments. Pressing the Button will replace the container with FragmentA. FragmentA contains a Button, pressing that will replace the container with FragmentB. FragmentB has a TextView which is stored in the fragment as an instance field.

enter image description here

This report is based on the following operation performed on the above application (Only the Views that I created i.e. ConstraintLayout, Framelayout, Button and TextView are taken in consideration),

  1. Opened the app: Activity visible
  2. Pressed the Button in the Activity: FragmentA visible
  3. Pressed the Button in FragmentA: FragmentB visible and FragmentA onDestroyView()
  4. Pressed the Button in the Activity: 2nd instance FragmentA visible and FragmentB onDestroyView(). (This is the same as step 2 in the previous example except, FragmentB acts as A and the 2nd instance of FragmentA acts as B)
  5. Pressed the Button in the 2nd instance FragmentA: 2nd instance of FragmentB visible and 2nd instance of FragmentA onDestroyView().
  6. Pressed Back Button: 2nd instance of FragmentA visible and 2nd instance of FragmentB onDetach()
  7. Pressed Back Button: 1st instance of FragmentB visible and 2nd instance of FragmentA onDetach()
  8. Pressed Back Button: 1st instance of FragmentAvisible and 1st instance of FragmentB onDetach()
  9. Pressed Back Button: 1st instance of FragmentA onDetach()
  10. Pressed Back Button: which closed the application.

Observation

If you look into the report you can see, in step 1, each and every view lived until the app is closed. In step 2, the View of FragmentA i.e. FrameLayout and it's child, Button are allocated and got both cleared in step 3 which is expected. In step 3, the View of FragmentB i.e. FrameLayout and its child TextView is allocated but did not get cleared in step 4 hence, caused memory leak but cleared in step 7 when it's View is created again and allocated newly created View. On the other hand, the Views that are created in step 5 just got cleared in step 6 causing no memory leak, because the fragment was detached and they didn't prevent the fragment from being cleared up.

Conclusion

We observed that the leak from saving views in fragment lasts until the user returns back to the fragment. When the fragment is brought back i.e. onCreateView() is called, the leak is recovered. On the other hand, no leak happens when the fragment is on top and can only go back. Based on it, we can make the following conclusion,

  • When there is no forward transaction from a fragment, there is nothing wrong with saving views as a strong reference as they will be cleared in onDetach()
  • If there is a forward transaction we can store weak references of views so that they are cleared in onDestroyView()

P.S. If you don't understand heap dump, please watch Google I/O 2011: Memory management for Android Apps. Also, this link provides valuable information about memory leak.

I hope my answer helped you clear your confusion. Let me know if you still have confusion?

like image 98
Roaim Avatar answered Nov 16 '22 00:11

Roaim