Memory Leaks with Fragments in Android (2024)

In the world of mobile, there are many resource constraints, memory being one of the most precious. When building an Android app, it is important to understand what memory leaks are, how they happen, and how to fix them. This article will go over a common leak when using Fragments in your Android app.

What is a memory leak? There are many definitions out there. Let’s establish the type of memory leak this article will discuss.

A memory leak occurs when an object’s reference is held on to after its purpose has been served. As a result, this prevents the garbage collector from cleaning up the reference.

How do leaks happen in fragments? First, we need to start by reviewing the important nuance of fragments. They have two different lifecycles:

  • It’s own lifecycle (onCreate and onDestroy)
  • It’s view’s lifecycle (onCreateView and onDestroyView)

Having two lifecycles for a single screen can be problematic. They are created and destroyed at different times, for instance, when putting a fragment on the back-stack. Specifically, holding onto views after onDestroyView is called will leak. This happens when a fragment is on the back stack, and although its view is destroyed, the fragment itself is not. The garbage collector is unable to clear the reference to those views.

Google does a great job explaining these lifecycles in a Google IO Talk about Fragments and in this StackOverflow post. The folks over at Square created a library called LeakCanary to help detect memory leaks, and I highly recommend using it in your app.

Let’s take a look at an example of memory leaks in a fragment.

class ExampleFragment : Fragment { // Both the view and adapter will leak memory
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: ExampleAdapter

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.example_fragment, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = ExampleAdapter()

recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = adapter
}
}

When you put ExampleFragment on the back stack, the fragment’s view is destroyed, but the fragment itself still exists in memory in the FragmentManager. In other words, onDestroyView is called, but onDestroy is not! If the Fragment is holding on to any views as member variables, and we are not nulling it out in the onDestroyView, then a memory leak occurs.

What’s happening here is that the Fragment still has a strong reference to the view even after it has already gone through the view destruction lifecycle, preventing it from being garbage collected. This is bad because all views have a strong reference to Context, and leaking that Context can basically leak anything in the app.

If you are using DataBinding or ViewBinding to consolidate your views, it can leak just the same as in the example above. We can see this in the ListViewBindingMemoryLeakFragment class in our memory leak Github Project. If you are leaking memory, then you will see a report like this from LeakCanary:

Memory Leaks with Fragments in Android (2)

Not only can the views/binding leak memory, but so can the RecyclerView’s adapter. If you keep a strong reference to the adapter via a lateinit var property, this will leak memory. LeakCanary also reports this scenario:

Memory Leaks with Fragments in Android (3)
Memory Leaks with Fragments in Android (4)

Taking a deeper look, we can see that the adapter holds onto a RecyclerViewDataObserver. This, in turn, holds onto the RecyclerView. In other words, we are keeping a view reference after its destruction!

One solution is to simply null out the references in the onDestroyView method. The only con is this requires the view variables to be nullable:

class ListViewBindingMemoryLeakFragment : Fragment {

private var binding: ListViewBindingMemoryLeakFragmentBinding? = null
private var adapter: ListViewBindingMemoryLeakAdapter? = null

// A non-null reference to the binding or adapter will leak memory
// private lateinit var binding: ListViewBindingMemoryLeakFragmentBinding
// private lateinit var adapter: ListViewBindingMemoryLeakAdapter

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = ListViewBindingMemoryLeakFragmentBinding.inflate(LayoutInflater.from(requireContext()), null, false)
return binding?.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = ListViewBindingMemoryLeakAdapter()
binding?.recyclerView?.adapter = adapter
}

override fun onDestroyView() {
super.onDestroyView()
binding = null
adapter = null
}
}

Wouldn’t it be nice not to worry about overriding onDestroyView and not having null checks everywhere you use the binding object or the adapter? So, let’s create something more scalable.

Here is an improvement to the above solution that uses Kotlin property delegation. Essentially, it will construct a ViewBinding object that is tied to the fragment view’s lifecycle. One thing to point out is that this requires DefaultLifecycleObserver, which comes from the lifecycle jetpack library, specifically the androidx.lifecycle:lifecycle-common-java8 in the build.gradle file.

fun <T : ViewBinding> Fragment.viewBinding(
viewBindingFactory: (View) -> T
): ReadOnlyProperty<Fragment, T> = object : ReadOnlyProperty<Fragment, T> {

private var binding: T? = null

init {
viewLifecycleOwnerLiveData.observe(this@viewBinding, Observer { viewLifecycleOwner ->
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
(binding as? ViewDataBinding)?.unbind()
binding = null
}
})
})

lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
view ?: error("You must either pass in the layout ID into ${this@viewBinding.javaClass.simpleName}'s constructor or inflate a view in onCreateView()")
}
})
}

override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
binding?.let { return it }

val viewLifecycleOwner = try {
thisRef.viewLifecycleOwner
} catch (e: IllegalStateException) {
error("Should not attempt to get bindings when Fragment views haven't been created yet. The fragment has not called onCreateView() at this point.")
}
if (!viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
error("Should not attempt to get bindings when Fragment views are destroyed. The fragment has already called onDestroyView() at this point.")
}

return viewBindingFactory(thisRef.requireView()).also { viewBinding ->
if (viewBinding is ViewDataBinding) {
viewBinding.lifecycleOwner = viewLifecycleOwner
}

this.binding = viewBinding
}
}
}

When the viewBinding delegate is initialized, it starts by observing the fragment view’s lifecycle. This is done to null out the binding object when the view’s lifecycle is destroyed. This pattern will always guarantee that the binding object will never leak after the fragment’s onDestroyView called.

// Must pass layout id to constructor of fragment or override onCreateView()
class ListViewBindingDelegateSolutionFragment : Fragment(R.layout.list_view_binding_delegate_solution_fragment) {

// Init view binding delegate. It will automatically
// null out the ViewBinding object internally.
private val binding by viewBinding(ListViewBindingDelegateSolutionFragmentBinding::bind)

// Adapter is also tied to view lifecycle so we cannot hold onto
// a strong reference to it or it will also leak memory. Use a
// computed property to evaluate getting the adapter every
// time you need it.
private val adapter get() = binding.exampleRecyclerView.adapter as ListViewBindingDelegateSolutionAdapter

// Must consume binding after it has been created, thus we must use
// onViewCreated rather than onCreateView or else we crash
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.exampleRecyclerView.adapter = ListViewBindingDelegateSolutionAdapter()
binding.exampleTextView.text = "Hello world!"

viewModel.exampleItemsLiveData.observe(viewLifecycleOwner) { items ->
adapter.setItems(items)
}
}
}

One caveat of this implementation is that the fragment’s view must be created in either the constructor or in the onCreateView method. All of the logic to set up the views, such as onClickListeners, must be done in onViewCreated. Accessing the binding object before onViewCreated is called will cause the app to crash because the DataBinding object inside that delegate hasn’t been set yet.

This solution does have one drawback, where the binding delegate cannot be accessed in onDestroyView method. The app will crash if it is accessed because the property for the binding has already been nulled out. So if you need to clean up your views, for instance, removing a paged change listener for a ViewPager, it will have to be done in onStop.

A RecyclerView’s adapter is tied to the view’s lifecycle, so you must make sure that you are in the correct lifecycle state when you set data to your adapter. For instance, if you are populating the adapter with data from a network call, make sure the view has been created when setting data when the response comes back. Setting data to the adapter outside the bounds of the view’s lifecycle will cause a crash.

A Github project has been created to showcase the memory leaks with the fragment’s view, as well as the leak happening with a RecyclerView’s Adapter. You can see that LeakCanary will report leaks in both ListAdapterMemoryLeakFragment as well as ListViewBindingMemoryLeakFragment. The ListViewBindingDelegateSolutionFragment is an example of how to use the solution to address the memory leak problem.

Google acknowledges the Fragment API’s flaws and is currently working on a solution to consolidate the two lifecycles into a single lifecycle. This solution may be released in the AndroidX Fragment 1.3.0 library. Depending on how they accomplish this, the solution described here may, in time, become obsolete.

The number of crashes related to memory has gone down as we have rolled out the solution, but we have not fully eradicated them. There are a multitude of reasons for our app crashing from memory constraints. The memory leaks were just one symptom of it.

EDIT (10/2/21): Google still hasn’t released the consolidated lifecycles in their Fragment library. Version 1.3.x nor 1.4.x have solved it yet. Hopefully they will release it somewhat soon.

Memory Leaks with Fragments in Android (2024)

FAQs

What causes memory leak on Android? ›

In Android development, using static views or contexts is a common cause of memory leaks. This happens when a static field holds a reference to a view or context, such as an Activity, which prevents the garbage collector from reclaiming the memory even after the context is no longer in use.

What are the best practices for memory leaks in Android? ›

Best Practices for Preventing Memory Leaks:

Handle Handlers and Runnables Carefully: Use weak references for handlers or remove them in lifecycle methods. Terminate threads properly and avoid creating unnecessary ones. Manage Bitmaps Correctly: Always call recycle() on bitmaps when you're done with them.

How do I find out what is leaking memory? ›

To find memory leaks and inefficient memory usage, you can use tools such as the debugger-integrated Memory Usage diagnostic tool or tools in the Performance Profiler such as the . NET Object Allocation tool and the post-mortem Memory Usage tool.

What is the root cause of memory leak? ›

Programming issues

Memory leaks are a common error in programming, especially when using languages that have no built in automatic garbage collection, such as C and C++. Typically, a memory leak occurs because dynamically allocated memory has become unreachable.

Is memory leak a hardware problem? ›

Memory leaks can occur due to programming errors, such as failing to release allocated memory, or due to inefficient memory management practices within a software application. Additionally, memory leaks can also be caused by hardware issues or driver problems that result in memory not being properly released.

Which of the following should be avoided to prevent memory leaks Android? ›

To avoid memory leaks, memory allocated on the heap should always be freed when no longer needed.

What are the tools for memory leak detection in Android? ›

LeakCanary is a memory leak detection library for Android. LeakCanary's knowledge of the internals of the Android Framework gives it a unique ability to narrow down the cause of each leak, helping developers dramatically reduce jank, Application Not Responding freezes and OutOfMemoryError crashes. Get started!

How do I free up memory leaks? ›

How to Fix Memory Leaks on Windows
  1. Close the Application Hogging Your System Memory and Restart Your Computer. ...
  2. Disable Startup Programs. ...
  3. Update Your Operating System and Device Drivers. ...
  4. Adjust Your Computer for Best Performance. ...
  5. Clear Your Paging File. ...
  6. Check for Malware. ...
  7. Check for Memory Issues.
Sep 24, 2023

When can you tell that a memory leak will occur? ›

A memory leak occurs when a process allocates memory from the paged or nonpaged pools, but doesn't free the memory. As a result, these limited pools of memory are depleted over time, causing Windows to slow down. If memory is completely depleted, failures may result.

What is the best tool to detect memory leaks? ›

In this tutorial, we will review what memory leak is exactly concerned with and how to deal with its tools.
  • #1) GCeasy.
  • #2) Eclipse MAT.
  • #3) Memcheck by Valgrind.
  • #4) PVS-Studio.
  • #5) GlowCode.
  • #6) AQTime by Smartbear.
  • #7) WinDbg.
  • #8) BoundsChecker.

Can memory leak cause crash? ›

If left unchecked, memory leaks can eventually lead to a system crash, often at the most inconvenient times.

What is an example of a memory leak? ›

When you create something dynamically / allocate memory on heap but then forget to delete/free that allocated memory (de-allocation) after using it, it's called memory leak. int main() { int var = new int(5); cout << var << endl; } // Here is memory leak as I didn't delete the memory from heap after using.

How do you isolate a memory leak? ›

By running load tests and utilizing the exclusion method, developers can identify the root cause of memory leaks and ensure proper isolation. Additionally, avoiding common mistakes and employing proper monitoring tools can go a long way in preventing the recurrence of previously fixed bugs.

What is the difference between a memory leak and a dangling pointer? ›

A dangling pointer situation occurs when a pointer refers to a memory that is no longer allocated, while memory leaks happen when we lose the pointer that is pointing to a memory block, making it impossible to free that block anymore.

How does a memory leak happen? ›

A memory leak occurs when a process allocates memory from the paged or nonpaged pools, but doesn't free the memory. As a result, these limited pools of memory are depleted over time, causing Windows to slow down. If memory is completely depleted, failures may result.

How do I fix my memory on my Android? ›

  1. Close apps that don't respond. You don't usually need to close apps. ...
  2. Uninstall apps you don't use. If you uninstall an app and need it later, you can download it again. ...
  3. Clear the app's cache & data. You can usually clear an app's cache and data with your phone's Settings app.

How do I fix Google memory leak? ›

Close tabs

The simplest way to lower your Chrome memory usage is to reduce the number of windows and tabs you have open. To close individual tabs: Click the small “X” on the tab you want to close. This button is located on the right side of the tab (left side if you're using a macOS).

Which of the following should be avoided to prevent memory leaks? ›

To avoid memory leaks, memory allocated on the heap should always be freed when no longer needed.

Top Articles
Latest Posts
Article information

Author: Nathanael Baumbach

Last Updated:

Views: 6575

Rating: 4.4 / 5 (75 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Nathanael Baumbach

Birthday: 1998-12-02

Address: Apt. 829 751 Glover View, West Orlando, IN 22436

Phone: +901025288581

Job: Internal IT Coordinator

Hobby: Gunsmithing, Motor sports, Flying, Skiing, Hooping, Lego building, Ice skating

Introduction: My name is Nathanael Baumbach, I am a fantastic, nice, victorious, brave, healthy, cute, glorious person who loves writing and wants to share my knowledge and understanding with you.