Minute of Pain #2. Leaks

Some time ago we discovered that the reason for our frequent OOM errors was the old version of Android Annotations library. We use a “One-Activity-Multiple-Fragments” pattern in our application development and put fragments into a common back stack that managed by FragmentManager. So for us, it is important to clear all references to views and view related objects in fragment’s onDestroyView method. Thus we can keep our fragments lean in memory. If we will not “nulliate” views, then they will be not destroyed and will be kept in App’s memory, littering it, and finally bringing us to Out of Memory Error.

Unfortunately, we used version 3.2 of Android Annotations lib and their ‘not cleaning views in fragments’ fix came out only with version 4.0. So we updated our library, but our issues were not solved completely. In this article, I will bring three major cases, when view were “nulliated” in fragment, but still were not destroyed. I will not discover something new horrible things. No, guys. I just want to point on pretty obvious thing that we all (at least me) can easily forget in a fit of refactoring :)

LayoutManager

Okey, this one is simple. LayoutManager already by the name of the class can arouse your suspicion about its relation to RecyclerView. And that’s true: if we go to the source code of base abstract class LayoutManager we will see, that RecyclerView presents as one of its fields.

public static abstract class LayoutManager {
ChildHelper mChildHelper;
RecyclerView mRecyclerView;
...

As it appears to be an apparent thing, I need to say that such kinds of Leaks are pretty embarrassing for me. But what is not easy, is to find it. If you have a big project, and you just received it from, for example, another team, you need to deal with the code that you didn’t write. Or maybe you wrote that thing yourself in a rush, afraid to miss the deadline. So then you need to eliminate a leak. To do it, you need to be sure that leak exists. And that is hard. You need to know common leak parts and just go to each Activity/Fragment/View-related-class and think, what you or your colleagues didn’t clean up. Or you go to Android Studio Memory Monitor and investigate where is memory don’t get freed. Then you capture a heap dump in that place. Then you wade through a huge amount of classes and references in attempts to understand, where is a chain of references from fragment to view.

So for me, ‘LayoutManager as a fragment field’ became one of the general things why memory leaks were not fully fixed after updating version of Android Annotation Library.

Adapter

This one relates to both ListView adapters and RecyclerView adapters. But I will be talking about RecyclerView’s one. Adapter for ListView, have an analogous mechanism.

You know adapter looks like a harmless creature. It just adapts data to UI item of a list. Pretty simple, isn’t it? And mostly that’s right. But still if we will not “nulliate” it in fragment’s onDestroyView then RecyclerView will be leaked, and thus whole view hierarchy (since every view contains references not only to its children but also to its parent).

Let’s go deep in RecyclerView.Adapter realization and we will see the next chain of references: adapter holds in itself field AdapterDataObservable mObservable ; and AdapterDataObservable is an inner class of RecyclerView, which means that instance of that class holds a reference to RecyclerView’s instance.

So, as with LayoutManager we need “nulliate” adapter in onDestroyView or just drop it from RecyclerView by calling setAdapter(null) method. But why we need to that for adapter, that is needed just for converting data to UI item view?

The reason is notifyDataSetChanged() method (also notifyItemInserted(int) and other notifying methods). Again, such thing is pretty obvious, but still, it is easy to forget about storing reference to RecyclerView inside the adapter.

MenuItem

After “nulliating” all views and cleaning all adapters, layout managers and others view related objects, it would seem, what else can leak view? Maybe that’s all? No. And this time I was framed by a MenuItem.

If you look at the name of this class, it seems pretty inoffensive. Well, yeah, it has method setActionView where you passing a view. But this is just isolated view. You didn’t attach it to whole view hierarchy, right? Right, but as at appeared, it happened later when MenuBuilder asks special menu presenter to update views for menu items. Then MenuPresenter (particulary it happened in BaseMenuPresenter) gets view through menuItem.getActionView() or creates it through MenuPresenter’s implementation. And do that for all menu items. And right here adding a view to MenuView implementation happens.

Thus, it can be logical to just call menuItem.setActionView(null) in onDestroyView method. But wait, we also have methods like setTitle or setIcon that accept resource identifiers. It looks suspicious. But, well…maybe then Menu or aforementioned MenuPresenter implementation just grabs those resource ids and used them, since they have access to context’s reference.

No. As it turns out, MenuItem’s implementation has a MenuBuilder instance as its field. And MenuBuilder contains implementation of some interface Callback, which is, as it appeared, private inner class MenuBuilderCallback of outer view class ActionMenuView. That’s the chain, that keeps a reference to ActionMenuView, thus this view doesn’t get cleaned and so whole hierarchy remains in memory.

And I understand that we could guess it, since, for example, Toolbar updates after you calling menuItem.setIcon(int) , so definitely there is notification mechanism in MenuItem implementation and direct or transitional reference to the view. But still, from my point of view, it is easy to forget and hard to remember. Especially, when fixing massive view leaks in your application.

Afterwords

I didn’t say you something new. I didn’t point to a lack of Android SDK architecture. Of course not. What I tried to say, is to we need always be attentive and careful when working with the code. No rush, no hurry. Especially when you refactor or try to fix memory leaks. And it’s not urgent that your code smells, so you need to be slow etc. No, I mean that always there can be things that are pretty obvious but we all can easily forget about them.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Michael Spitsin

Michael Spitsin

Love being creative to solve some problems with an simple and elegant ways