In our daily pursuit of building better apps, we as developers need to take many things into consideration in order to stay on track, one of which is to make sure that our apps do not crash. A common cause of crashes are memory leaks. This particular problem can manifest itself in various forms. In most cases we see a steady increase in memory usage until the app cannot allocate more resources and inevitably crashes. In Java this often results in an OutOfMemoryException being thrown. In some rare cases, leaked classes can even stick around for long enough to receive registered callbacks, causing some really strange bugs and all too often throw the notorious IllegalStateException.
To help others minimize time spent on code analysis I’ll present a few examples of memory leaks, how to identify them in Android Studio and most importantly how to remove them.
The purpose of this post and its code examples is to promote a deeper understanding of memory management, especially in Java. The general architecture, management of threads and handling of running HTTP requests shown are not ideal for a production environment and merely serve as carrier of the point being made: Memory leaks in Android is a thing to be considered.
This shouldn’t really be a problem but all too often I see calls to various register methods but their unregister counterparts are nowhere to be seen. This is a potential source of leaks, since these methods are clearly designed to be balanced off by each other. Without calling the unregister method, the instance will probably keep a reference around long after the referenced object has been terminated and will thus start leaking memory. In Android this is especially troublesome if that object is an activity, since they often hold a large amount of data. Let me show you what that might look like.
In this example we’re letting the Android LocationManager inform us about location updates. All we need to set this up is the system service itself and a callback to receive the updates. Here we implement the location interface in the activity itself, meaning that the LocationManager will hold a reference to our activity. Now if the device were to be rotated, a new activity would be created replacing the old one already registered for location updates. Since a system service will most definitely outlive any activity, the LocationManager will still hold a reference to the previous activity, making it impossible for the garbage collector to reclaim the resources still tied to that particular activity, resulting in memory being leaked. Repeated rotation of the device will then cause non-reclaimable activities to fill up the memory, ultimately leading to an OutOfMemoryException.
But in order to fix a memory leak, we first have to be able to find it. Luckily Android Studio has a built in tool called Android Monitor we can use to observe memory usage among other things. All we really have to do is open the Android Monitor and go to the Monitors tab to see how much memory is used and allocated in real time.
Any interactions causing resource allocation will be reflected here, making it an ideal place for keeping track of your application’s resource usage. In order to find our memory leak, we need to know what the memory contains at a point in time when we suspect that memory has been leaked. For this particular example, all we have to do is start our application, rotate the device once and then invoke the Dump Java Heap action (next to Memory, the third icon from the left). This will generate a hprof file, which contains a memory snapshot at the time we invoked the action. After a couple of seconds, Android Studio automatically opens up the file, giving us a neat visual representation of the memory for easy analysis.
I will not go into depth about how to navigate the huge memory heap. Instead I’ll direct your attention to the Analyzer Tasks in the upper right corner of the screenshot below. All you have to do to detect the memory leak introduced in the example above is to check Detect Leaked Activities and then press play to get the leaked activity to show up under Analysis Results.
If we select the leaked activity we are presented with a Reference Tree where the reference that is keeping the activity alive can be identified. By looking for instances with depth zero we find that the instance mListener located within the location manager is the reason our activity can’t be garbage collected. Going back to our code we can see, that this reference is due to the requestLocationsUpdates where we set the activity as a callback for the location updates. Consulting the documentation on the location manager it quickly becomes clear that in order to unset the reference we must simply call the removeUpdates method. In our example since we register for updates in the onCreate method, the obvious place to unregister would be in the onDestroy method.
Rebuilding the application and performing the same memory analysis as above should result in no leaked activities regardless of how many times the device is rotated.
A very common data structure in Java is the so called inner classes. They are popular since they can be defined in such a way, that only the enclosing class can instantiate them. What many may not realize is the fact that these kind of classes will create an implicit reference to the enclosing class, which may not always be what you want. Unintentional referencing is very prone to errors, especially if the two classes have different life cycles. Consider the following common Android activity.
This particular implementation will work just fine. The problem is that it will most definitely retain memory longer than necessary. Since the BackgroundTask keeps an implicit reference to the AsyncActivity while also running on another thread which lacks cancellation policy the activity will remain in memory with all of its resources attached until the background thread has terminated. In case of a HTTP request, this can take a long time, especially on a slow connection.
By performing the same steps as in the previous example, with the addition of ensuring a long running background task, we end up with the following analysis results.
As seen in the analysis above, the BackgroundTask is indeed the culprit behind this memory leak. First order of business is to eliminate the unintended reference to the activity by making the class static but by doing so we also cannot directly access the textView anymore. Therefore we also need to add a constructor in order to pass the view on to the task. Finally we need to introduce a cancellation policy for the task, which is described in the AsyncTask documentation. Taking all of that into account, let’s see what our code ends up like.
Now that the implicit reference has been eliminated, we pass the single relevant instance to the task via the constructor. With our cancellation policy in place, let’s run the analyzer task once more to see if this change eliminated the memory leak.
It seems we still have some work to do. By applying the same technique as in the last example we can identify the highlighted instance in the Reference Tree as the one keeping the activity alive. So what’s going on here? If we look closer at its parent node we see that the view has a reference to mContext which is nothing other then our leaked activity. So how to solve this? We can’t eliminate the context, to which the view is bound and we need the view reference in the BackgroundTask in order to update the UI. One simple way to solve this would be to use a WeakReference. Our reference to the resultTextView is considered strong and has the ability to keep the instance alive preventing garbage collection. In contrast, a WeakReference will not keep its referenced instance alive. As soon as the last strong reference to an instance has been removed, the garbage collector will reclaim its resources regardless of any weak references to this object. Here is what the final version will look like using WeakReference:
Notice that in onPostExecute we have to check for null to verify, if an instance has been reclaimed or not.
Finally, running the analyzer task once more will confirm that our activity is no longer being leaked!
These kinds of classes have the same disadvantages as the inner class, namely they hold a reference to the enclosing class. Just as inner classes, anonymous classes can be sources of memory leaks when passed to an instance that operates outside of the activity’s life cycle or performs work on another thread. For this example I’ll use the popular HTTP client Retrofit to perform an API call and pass the response to a callback. The client is set up like the example on the Retrofit homepage. I’ll also keep the reference to the GitHubService in the application instance, which is not a particular good design choice but serves the purpose of this example.
This is a common type of solution and may not result in any leaks. But if we execute this on a slow connection the analyzer result will be different. Remember, the activity is kept until the thread has terminated, just as in the inner class example.
Following the same line of reasoning as in the inner class example we arrive at the conclusion, that the anonymous callback class is the reason for our memory leak. However, just as the inner class example this code contains two problems. First of all, there is no cancelling policy for the request. Secondly, the implicit reference to the activity needs to be dissolved. Taking this into account an obvious solution would be to do the same thing we did in the inner class example.
Running the analyzer task on this solution will result in no leaked activities.
Background tasks running independently of the activity life cycle can be a hassle. Add to that the need to coordinate the flow of data between the UI and various background tasks and you have a recipe for disaster if you’re not careful. Be aware of what you’re doing and of the performance impact your code might have. A good start would be to consider these general guidelines when dealing with activities:
- Favor static inner classes over non-static. Each non-static inner class will have an implicit reference to its outer class instance which may result in unwanted behaviors. Instead define these classes as static and store life cycle references needed as WeakReferences.
- Consider other means of background service. Android offers a wide variety of ways to get work off the main thread such as HandlerThread, IntentService and AsyncTask, each with their own strengths and weaknesses. In conjunction, Android offers several mechanisms to propagate information back to the main thread in order to update the UI. The BroadcastReceiver is one very capable tool to achieve this.
- Do not blindly rely on the garbage collector. When coding in a garbage collected language it’s easy to make the assumption, that memory management does not have to be taken into consideration. Our examples clearly show that this is not the case. Therefore, make sure that your allocated resources are all collected as expected.
Want to learn more?
Are you just getting started with memory and performance management in Android? Then consider checking out the following resources for more information on how to build better apps and become a better Android developer.
- Android Performance Patterns on YouTube
- Avoiding memory leaks from the Android Developer Blog
- LeakCanary — A memory leak detection library for Android and Java
UPDATE: With the help of some friendly remarks from the Android community I’ve adjusted the example code to follow customary best practices.
Interested in how we develop software? Join our team in Hamburg, Germany!