One of the most common myths They told us about programming in a Garbage collector environment is there is no need to worry about memory management. Ok, that is usually right most of the time but it is not complete truth (in fact, memory leaks in GC environments are pretty difficult to spot). There are some potencial sources of memory leaks and some of them are not obvious and they can lead to a huge memory leakage.
There is a well-known potential cause of memory leaks on Android which it is usually called “Leaking the Context” and it is a very problematic source of leaking memory because, as you know, an Activity (inherited from Context) is a potential god-object with a lot of references and it has to be destroyed and re-created every time you change the orientation of the screen.
Although it is a common issue is not usually well explained, or explained at all in Android tutorials or introductory books. There are some good links with amazing information about this issue but they usually lack of examples.
So I am going to take a “learning by example” approach to make you and extraordinary and merciless hunter of memory leaks.
The one you are going to see in every Memory leak example out there
This is the most common and easiest example of a memory leak you are going to find. First let´s take a look at this piece of code
public class MainActivity extends Activity { static LeakMeNow leak; // static classes can be GC roots @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(leak == null) { leak = new LeakMeNow(); leak.logSomethingLeaky(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } private class LeakMeNow { // This class holds a reference of Activity public void logSomethingLeaky() { Log.i("LeakExamples", "I know you are going to leak something here, just shake your phone"); } } }
It looks pretty simple and unoffensive isn’t it? It is not so innocent, it hides a memory leak. LeakMeNow class is an inner class and all inner classes keep a reference of their enclosing classes. This leads to a memory leak if, as it is happening in this example, a static reference of this inner class has been declared. Fortunately this example is really simple to solve. We have two options.
Make the reference non-static:
LeakMeNow leak; // Non static class, safe now
If you really need a static reference and need access to variables from Activity, you have to make your inner class static and pass a WeakReference to your activity.
private static class LeakMeNow { // It is not going to leak now private WeakReference upperClassReference; private LeakMeNow(MainActivity activity) { upperClassReference = new WeakReference(activity); } // This class holds a reference of Activity public void logSomethingLeaky() { Log.i("LeakExamples", "I know you are going to leak something here, just shake your phone"); } }
Threads are dangerous too!
Threads are a potential cause of leaks too. Let’s see this code:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new LeakyThread().start(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } public class LeakyThread extends Thread { public void run() { synchronized(this) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
Every time onCreate is called a new thread is going to be created and started. When orientation is changed those threads will remain and, consequently the Activity is not available for collection.
Best solution is being careful with your thread lifecycle and stop them when necessary. In this particular example is really easy:
public class MainActivity extends Activity { private LeakyThread thread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); thread = new LeakyThread(); thread.start(); } @Override public void onDestroy() { super.onDestroy(); thread.stopThread(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } public class LeakyThread extends Thread { public void run() { synchronized(this) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void stopThread() { synchronized(this) { notify(); } } } }
Basically you don’t have to create threads as anonymous classes and it is imperative to stop them before the destruction of the Activity.
A really subtle one (based on real events)
The third leak is going to be more difficult to spot. Although it involves a thread and We already know they are prone to problems, the thread involved here is not a subclass or an anonymous class of the Activity, it is completely separated of it. It means this thread should not be a GC root of the Activity (threads are potential candidates to be roots) but IT IS THE GC ROOT!!! Some code:
public class SemperVigilans { // This class, as techie new form of inquisition, it is going to check the incoming arrival of the end private ViewGroup viewGroup; private SemperVigilansThread thread; public SemperVigilans(ViewGroup viewGroup) { this.viewGroup = viewGroup; this.thread = new SemperVigilansThread(); this.thread.start(); } private class SemperVigilansThread extends Thread { public void run() { while(viewGroup.getWidth() != 666 && viewGroup.getHeight() != 666) { // World is save...at least for now } // Here a callback to warn the world about incoming apocalypse would be awesome } } }
Our Activity:
public class MainActivity extends Activity { private RelativeLayout layout; private SemperVigilans semperVigilans; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LayoutInflater inflater = getLayoutInflater(); layout = (RelativeLayout) inflater.inflate(R.layout.activity_main, null); semperVigilans = new SemperVigilans(layout); } @Override public void onDestroy() { super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
When this code is executed and orientation is changed you leak all the Activity but..Why? Well it is happening the same problem as before, but in a more subtle way. SemperVigilansThread is getting a reference from Activity through the Layout reference passed when SemperVigilans is created. Because Views objects have always a reference to the Context and in this case this reference reaches this thread. The solution is the same, control the lifecycle of your thread. If you remove all references to the Context but still your lifecycle is not controlled, you are still leaking but not the entire Activity.
View objects and Drawables objects are dangerous objects because both of them hold references to the Context. Drawables are even more dangerous because their references to their context are even less obvious Be careful with them.
Memory leaks are not exclusive of Activities
Leaking the context is probably the worse of all memory leaks you can get in Android but it is worth to mention that is not the only kind of leak you can get. As seen before, inner classes and anonymous classes are going to be naughty here:
public class LeakGenerator { // A leak generator public LeakGenerator() { } public Leak generateLeak() { return new Leak(); } public class Leak { public Leak() { } } }
MainActivity:
public class MainActivity extends Activity { private Button firstTimeButton, everyTimeButton; private List<Leak> listOfLeaks; private LeakGenerator generator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); firstTimeButton = (Button) findViewById(R.id.buttonfirsttime); everyTimeButton = (Button) findViewById(R.id.buttoneverytime); listOfLeaks = new ArrayList<Leak>(); firstTimeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(generator == null) { generator = new LeakGenerator(); } listOfLeaks.add(generator.generateLeak()); } }); everyTimeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { listOfLeaks.add(new LeakGenerator().generateLeak()); } }); } @Override public void onDestroy() { super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
This example is really easy, two buttons, one of them leaks one LeakGenerator() the first time you pressed and the other leaks a new LeakFactory() when pressed.
The first button only leaks the reference to LeakGenerator(). Even if the generator is not going to be use anymore it is going to live because every single leak reference has a reference to the generator. We can infer from this example that Objects from inner classes can’t outlive the outer class object which they hold a reference
The second button is a worse thing because every Leak object has a reference to a different generator.
That is all I have to say about this ugly issue I know you will face as an Android developer, it is better to be prepared before hunt them down.
Here some great essential links about Memory leaks:
Google I/O 2011: Memory management for Android Apps
Activities, Threads, & Memory Leaks
I hope you find useful this post. If you have some suggestions or you found some bugs just let me now 🙂
Happy Craft!