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.
The purpose of this post 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.
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.
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.
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.
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.