Memory Leaks in Android

In this Memory Leaks in Android tutorial, you’ll learn how to use the Android Profiler and LeakCanary to detect common leaks and how to avoid them. By Fernando Sproviero.

Leave a rating/review
Download materials
Save for later
Share

Memory leaks are a common cause of crashes in Android apps. Every Android developer should understand them and know how to avoid them.

In this tutorial you’ll learn how to use Android Profiler and LeakCanary to detect memory leaks.

Note: This tutorial assumes you have previous experience with developing for Android in Kotlin. If you’re unfamiliar with the language have a look at this tutorial. If you’re beginning with Android, check out Getting Started and other Android tutorials.

Android Profiler and LeakCanary

Android Profiler replaces Android Monitor tools and comes with Android Studio 3.0 and latter. It measures several app performance aspects in real-time like:

  • Battery
  • Network
  • CPU
  • Memory

LeakCanary is a library designed to detect memory leaks. You’ll learn what they are, common cases and how to avoid them.

In this tutorial you’ll focus on memory analysis to detect leaks.

Memory Leaks

A memory leak happens when your code allocates memory for an object, but never deallocates it. This can happen for many reasons. You’ll learn these causes later.

No matter the cause, when a memory leak occurs the Garbage Collector thinks an object is still needed because it’s still referenced by other objects. But those references should have cleared.

Note: If you want to learn more about the Garbage Collector and Android Memory Management, check out this tutorial.

The memory allocated for leaked objects acts as an immovable block, forcing the rest of the app to run in what’s left of the heap. This can cause frequent garbage collections. If the app continues to leak memory, it’ll eventually run out of it and crash.

Sometimes, a leak is big and obvious. Other times it’s small. Smaller leaks are more difficult to find because they usually occur after a long session of app usage.

Getting Started

To get started, download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

Throughout this tutorial you’ll work with TripLog and GuessCount.

TripLog lets the user write notes about what they’re doing and feeling during a trip. It also saves the date and location.

GuessCount is a game that asks you to internally count to a number in seconds. You press a button when you think the count is over. Then the game tells you the difference in seconds between your count and the actual count.

Open Android Studio 3.4.1 or later and click File ▸ New ▸ Import Project. Select the top-level project folder for one of the starter projects you downloaded.

Alternatively, to open one of the projects, select Open an existing Android Studio project from the Welcome screen. Again, choose the top-level project folder for one of the starter projects you downloaded.

Build and run TripLog and GuessCount to become familiar with them:

The TripLog project contains the following main files:

  • MainActivity.kt contains the main screen. It’ll show all the logs here.
  • DetailActivity.kt allows the user to create or view a log.
  • MainApplication.kt provides dependencies for the activities: a repository and formatters.
  • TripLog.kt represents the data of a log.
  • Repository.kt saves and retrieves logs.
  • CoordinatesFormatter.kt and DateFormatter.kt format the data to show in the screens.

The GuessCount project has these files:

  • MainActivity.kt contains the main screen. It lets the users set a number in seconds to count.
  • CountActivity.kt lets the user press a button when he or she thinks the count is over.

Finding a Leak Using The Android Profiler

Start profiling the GuessCount starter app using the Android Profiler. You can open it by going to View ‣ Tool Windows ‣ Profiler.

android profiler button

Note: You could also build and directly profile the app by going to Run ‣ Profile ‘app’ or pressing the Profile button in the Navigation Bar.

Enter 240 seconds to the Seconds to count field and press the Start button. You’ll see a small loading screen. Here, you’re supposed to count to 240 and press Guess, but instead, press the Back button.

In the profiler, generate a heap dump by pressing the Dump Java heap button:

android memory profiler dump heap

Filter by CountActivity. You’ll see this:

android memory profiler count activity not deallocated

The CountActivity wasn’t deallocated, despite not showing in the screen. It’s possible the Garbage Collector hasn’t passed yet. So, force a few garbage collections by pressing the corresponding button in the profiler:

android memory profiler force gc

Now, generate a new heap dump to check if it was deallocated:

android memory profiler count activity not deallocated after gc

No, it wasn’t deallocated. So, click on the CountActivity row to see more detail:

android memory profiler count activity not deallocated after gc detail

In the Instance View, you’ll see that according to the mFinished property, the activity already finished. So the Garbage collector should have collected it.

This is a memory leak because the CountActivity is still in memory but nobody needs it.

Wait a few minutes. Repeat the process of forcing a garbage collection and generating a heap dump. You’ll see that finally the activity is deallocated:

android memory profiler count activity deallocated

This is actually a temporal memory leak. In this example, temporarily leaking the CountActivity isn’t a big problem. But remember, leaking an entire activity could be really bad because you’d retain all its views and every object it references. This is also true for anything that has a reference to an object as well, such as a TextView.

Note: The Garbage Collector can’t deallocate objects the leaked activity references because it thinks this activity still needs them.

To analyze this problem, open the first heap dump where the CountActivity was still retained even though you closed it. Do this by selecting it:

android memory profiler select first heap dump

Filter by CountActivity and select it. Then select the instance in the Instance View and you’ll see the following:

android memory profiler timeoutRunnable

The list in the References pane shows all the objects that have a reference to the CountActivity. The first one, this$0 in CountActivity$timeoutRunnable$1 is a variable from CountActivity. So, open the CountActivity.kt file and search for this variable:

  private val timeoutRunnable = object : Runnable {
    override fun run() {
      this@CountActivity.stopCounting()
      this@CountActivity.showTimeoutResults()
    }
  }

This variable holds a reference to an anonymous class of the Runnable interface. In anonymous classes you can get reference to your container, in this case the CountActivity.

That’s why this code can call stopCounting() and showTimeoutResults(). If you right click over the timeoutRunnable variable and select Find Usages, you’ll see that it’s called from this method:

  private fun startCounting() {
    startTimestamp = System.currentTimeMillis()
    val timeoutMillis = userCount * 1000 + 10000L
    timeoutHandler.postDelayed(timeoutRunnable, timeoutMillis)
  }

startCounting() is called from onCreate(). Here, you see a timeoutHandler that delays the execution of the timeoutRunnable. The app uses this timeout if you don’t press the Guess button.

Continue investigating the CountActivity class and you’ll see that timeoutHandler never cancels when you exit the activity. Therefore, it’ll execute what’s inside timeoutRunnable after timeoutMillis. In this case, that’s the stopCounting() and showTimeoutResults() methods from CountActivity. It has to retain it, generating the leak.