Memory Leaks in Android

Tram Ho

Source: https://www.raywenderlich.com/4690472-memory-leaks-in-android

Android Profiler and LeakCanary

Android Profiler replaces the Android Monitor tool, appearing from Android Studio 3.0 and later. It will measure several apps and perform real-time performance ratios:

  • Battery
  • Network
  • CPU
  • Memory LeakCanary is a library designed to detect memory leaks. You will learn what it is, common cases to prevent them.

In this tutorial, we will focus on memory analysis to detect leaks.

Memory Leaks

A memory leak occurs when in the code you initialize a memory cell for an object, you do not destroy it. It can happen for many reasons. You will learn about these causes later.

Regardless of the cause, when a memory leak occurs, the Garbage Collector will assume that the object is still needed because it is still referenced by another object. But these references should have been removed.

The initialized memory for the leaked object will act as a non-movable block, forcing the rest of the app to run in the rest of the heap. It will cause a collection of unnecessary information. And if the app continues to leak memory, it will be full and lead to a crash.

Sometimes, leaks are huge and dangerous. Change when it is a small error. Small bugs are harder to find because they usually happen after a long time using the app.

Begin

To get started, download the project in the source link.

Through this tutorial, you will work with TripLog and GuessCount .

TripLog will let users write notes about what they are doing and feeling during the process. It also stores the date and location.

GuessCount is a game that will ask you to count an internal number for a few seconds. You press a button when you think the count has passed. The game will then tell you the difference between the number you count and the actual count in seconds.

Open Android Studio 3.4.1 or later, select File ▸ New ▸ Import Project . Select the top-level of the project directory for one of the starter projects you have downloaded.

Build and run TripLog and GuessCount to get used to it:

TripLog project contains the following main files:

  • MainActivity.kt contains the main screen. It will display the log here.
  • DetailActivity.kt allows users to create or view logs.
  • MainApplication.kt provides activity dependencies: a repository and a formatter.
  • TripLog.kt represents the data for the log.
  • Repository.kt save and get log.
  • CoordinatesFormatter.kt and DateFormatter.kt format data to display on the screen.

The GuessCount project has the files:

  • MainActivity.kt contains the main screen. It allows the user to set the number of seconds to count.
  • CountActivity.kt allows the user to press a button when the user thinks it has finished.

Find Leak using Android Profiler

Use the Android Profiler to start profiling the GuessCount app. You can open it by going to View ‣ Windows Tool ‣ Profiler.

Enter 240 seconds into the Seconds to count field and click the Start button. You will see a small loading screen. Here, you expect to count to 240 and press Guess, but instead, press the Back button.

In the profiler, create a heap dump by clicking the Dump Java heap button:

Filter by CountActivity : You will see:

CountActivity has not been canceled, although it is not displayed on the screen. It is possible that the Garbage Collector has not passed. Therefore, tie some garbage collections by clicking the feedback button on the profiler:

Now create a new heap dump to check if it has been canceled:

And it is not canceled. So, click on CountActivity to see more details: In Instance View , you will see and according to the mFinished property, this activity has ended. So the Garbage collector should have noted it already.

It’s a memory leak because CountActivity is still in memory but nobody needs it.

Wait a few minutes. Repeat the process and create a new heap dump. You will see that the activity is finally canceled:

It is actually a temporal memory leak . In this example, the temporary CountActivity leak is not a big deal. But remember, leaking the entire activity is really bad, because it will contain all the views and objects it references. The same is true for all other objects referencing it, such as TextView .

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

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

The References list will show all objects referencing CountActivity. The first one, this$0 in CountActivity$timeoutRunnable$1 is possible from CountActivity. So, open the CountActivity.kt file and find this variable:

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

That is why the code can call stopCounting() and showTimeoutResults() . If you right click on the timeoutRunnable variable and select Find Usages , you will notice that it is called from the method:

startCounting() is called from onCreate() . Here, you will see a timeoutHandler that has timeoutHandler the execution of timeoutRunnable . The App uses this timeout if you do not press the Guess button.

Keep exploring the CountActivity class and you’ll see that the timeoutHandler is never called when you exit the activity. However, it will actually execute what is inside timeoutRunnable after timeoutMillis . In this case, it called stopCounting () and showTimeoutResults () from CountActivity. It will have to hold back, causing a leak.

Common Memory Leaks

Anonymous Classes

This is when an anonymous class instance lasts longer than the container instance. If so, the anonymous class instance will call any method, or read or write any attribute of the container class, it will retain the instance of the container. This will cause a leak memory of the container.

How Leak Canary shows Memory Leak

Before fixing a leak, you’ll use LeakCanary to see how this tool displays when there’s a leak. Open build.gradle file and add the following to your dependence:

Build and run the app, click the Start button and immediately click back. You should see the following:

The LeakCanary screen shows a destroyed activity. It will wait 5 seconds and then force garbage collection. If the activity persists, LeakCanary considers keeping and assessing the possibility of a leak.

Click on the notification and it will start dumping the heap:

After dumping the heap, LeakCanary will find the shortest strong reference from the GC root to retain the instance. It is also called a leak trace .

Click on the notification. Wait for a few seconds while it analyzes the heap dump. Click on the notification again and through the LeakCanary app to see the leak trace:

At the bottom, it says CountActivity is leaked because mDestroyed is true . It is similar to the result that you have analyzed before with the Android profiler.

At the top, you will see an instance of MessageQueue without being leaked. The reason is that it is a root GC.

LeakCanary uses diagnostics to determine the lifecycle status of the node chain and tell if it is leaking or not. A reference after the last Leaking: NO and before Leaking: YES caused the leak. The lines marked by red lines are candidates.

If you go from top to bottom, you’ll find CountActivity$timeoutRunnable$1.this$0 Again, it’s the same as the variable you found when using Android Profiler. It recognizes that the timeoutRunnable is referencing the activiry and Garbage Collector has not been canceled.

To fix this leak, open CountActivity.kt and add the following:

The stopCounting() function calls timeoutHandler.removeCallbacksAndMessages() .

Build and run the app again to see if the leak has been fixed with both Android Profiler and Leak Canary.

Inner Classes

Inner Classes is a common memory leak error because it can also refer to its container class. For example, suppose you have AsyncTask in onDestroy () as shown below:

This will cause a leak if you leave the activity and the task not finished. You can try closing AsyncTask in onDestroy() . However, because of the way AsyncTask works, the doInBackground() function will not close and you will continue to leak the activity.

To fix it, you will remove the inner to convert it into MyTask into a static class. The static inner class doesn’t need access to the container class, so you won’t be able to leak activity. But you will not be able to call showResult anymore.

So, you can think of passing activity as a parameter as follows:

However, you may also leak activity. A possible method is to use WeakReference :

A WeakReference references an object as a regular reference, but it is not strong enough to hold in memory. However, through the Garbage Collector and did not find any strong reference to the object. Any WeakReference references to a collected object will be set to null .

Static Variables

The variable inside the companion object is a static variable. There are variables that will interact with a class rather than an instance of the class. Therefore, they will exist from the system load class.

You should avoid containing static variable references in the activity. They will not be garbage collected even though they are no longer needed.

Singletons

If the singleton object holds a reference to an activity and lasts longer than your cyar activity, you will be leaked.

As a workaround, you can provide a method in a singleton object that will delete that reference. You can call that method in the activity of onDestroy() .

Registering Listeners

In many Android APIs and extended SDKs, you must register an activity as a listener and provide a callback method to a receiving event. For example, you will need to do this with location update and system event.

It may create a memory leak if you forget to unsubscribe. Usually, the object you register will not last longer than your activity.

To see this type of memory leak in practice, open the TripLog project. Build and run it.

Click the + sign to add a new log. Agree to grant the location permission and you will see it showing the user’s location. You may need to turn on the device’s Location service and try to log a few more times to see the user’s location:

Open the Android Profiler and wave the profiling app. Press Back, force the garbage collection a few times and create a heap dump.

You will notice that the DetailActivity still exists.

If you want, add LeakCanary to your dependence and check for leaks.

To fix it, you need to add the following:

Profile app again or use LeakCanary . Add new log.

Exit the app and force garbage collection. You will notice that there is still a leak, but it is a different leak than before.

This time the problem is due to the play-services-location library you are using.

To fix it, create a WeakLocationCallback.kt file with the following content:

Open DetailActivity.kt and replace the line where you set locationCallback with the following:

Build and run again to check if there’s a leak

Share the news now

Source : Viblo