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.
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.
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.
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,
On open FragmentA
: It's content/root View
and the TextView
will be allocated into the memory.
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
.
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.
On Back press from FragmentA
: The new allocations will be cleared as
well.
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.
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.
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),
FragmentA
visibleFragmentB
visible and FragmentA
onDestroyView()
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)FragmentB
visible and 2nd instance of FragmentA
onDestroyView()
.FragmentA
visible and 2nd instance of FragmentB
onDetach()
FragmentB
visible and 2nd instance of FragmentA
onDetach()
FragmentA
visible and 1st instance of FragmentB
onDetach()
FragmentA
onDetach()
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.
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,
onDetach()
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With