I have been an android developer for quite some time now. And I realised（意识到） that most of that time, I tend to spend on adding new features to an app or working on visually enhancements of the app, rather than focusing on the core issues like performance and quality.

This was me a while back when I would be asked to optimise or refactor code

But these things have a way of catching up to you and I spent a long time wishing I had not ignored the important stuff. So in this article, I thought I would focus on one of the most important optimisation techniques in android: Memory leaks.

There are a lot of articles on memory leaks and how to fix them. But when I was learning myself, I found that none of them were in one place and it was hard to keep track of it all. So I thought I would collectively post an article on it so that it might help people in the future.

So let’s begin.

什么是内存泄露？ （What are memory leaks?）

A memory leak happens when memory is allocated but never freed. This means the garbage collector is not able to take out the trash once we are done with the takeout. Initially, this might not be a problem. But imagine if you don’t take the trash out for 2 weeks! The house starts to smell right?

Similarly, as the user keeps on using our app, the memory also keeps on increasing and if the memory leaks are not plugged, then the unused memory cannot be freed up by the Garbage Collection. So the memory of our app will constantly increase until no more memory can be allocated to our app, leading to OutOfMemoryError which ultimately crashes the app.

那么，如何检查我的应用是否泄露？(So how do I check if my app is leaking?)

I am sure everyone on the planet is aware of it but just in case, there is an awesome library called LeakyCanary that is great for finding out the leaks in our app along with the stack trace.

那么，导致内存泄露的常见错误有哪些呢？(So what are some of the common mistakes that lead to memory leaks?)

Consider this scenario - you need to register a local broadcast receiver in your activity. If you don’t unregister the broadcast receiver, then it still holds a reference to the activity, even if you close the activity.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45  public class BroadcastReceiverLeakActivity extends AppCompatActivity { private BroadcastReceiver broadcastReceiver; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); } private void registerBroadCastReceiver() { broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //your receiver code goes here! } }; registerReceiver(broadcastReceiver, new IntentFilter("SmsMessage.intent.MAIN")); } @Override protected void onStart() { super.onStart(); registerBroadCastReceiver(); } @Override protected void onStop() { super.onStop(); /* * Uncomment this line in order to avoid memory leak. * You need to unregister the broadcast receiver since the broadcast receiver keeps a reference of the activity. * Now when its time for your Activity to die, the Android framework will call onDestroy() on it * but the garbage collector will not be able to remove the instance from memory because the broadcastReceiver * is still holding a strong reference to it. * */ if(broadcastReceiver != null) { unregisterReceiver(broadcastReceiver); } } } 

How to solve this? Always remember to call unregister receiver in onStop() of the activity.

Note: As Artem Demyanov pointed out, if the broadcast Receiver is registered in onCreate(), then when the app goes into the background and resumed again, the receiver will not be registered again. So it is always good to register the broadcastReceiver in onStart() or onResume() of the activity and unregister in onStop().

2.静态Activity或者静态View引用（2. Static Activity or View Reference:）

Consider the below example — You are declaring a TextView as static (for whatever reason). If you reference an activity or view directly or indirectly from a static reference, the activity would not be garbage collected after it is destroyed.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  public class StaticReferenceLeakActivity extends AppCompatActivity { /* * This is a bad idea! */ private static TextView textView; private static Activity activity; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); textView = findViewById(R.id.activity_text); textView.setText("Bad Idea!"); activity = this; } } 

How to solve this? Always remember to NEVER use static variables for views or activities or contexts.

3.单例类引用（3. Singleton Class Reference:）

Consider the below example — you have defined a Singleton class as displayed below and you need to pass the context in order to fetch some files from the local storage.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26  public class SingletonLeakExampleActivity extends AppCompatActivity { private SingletonSampleClass singletonSampleClass; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* * Option 1: Do not pass activity context to the Singleton class. Instead pass application Context */ singletonSampleClass = SingletonSampleClass.getInstance(this); } @Override protected void onDestroy() { super.onDestroy(); /* * Option 2: Unregister the singleton class here i.e. if you pass activity context to the Singleton class, * then ensure that when the activity is destroyed, the context in the singleton class is set to null. */ singletonSampleClass.onDestroy(); } } 

SingletonSampleClass.java

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  public class SingletonSampleClass { private Context context; private static SingletonSampleClass instance; private SingletonSampleClass(Context context) { this.context = context; } public synchronized static SingletonSampleClass getInstance(Context context) { if (instance == null) instance = new SingletonSampleClass(context); return instance; } public void onDestroy() { if(context != null) { context = null; } } } 

How to solve this? Option 1: Instead of passing activity context i.e. this to the singleton class, you can pass applicationContext(). Option 2: If you really have to use activity context, then when the activity is destroyed, ensure that the context you passed to the singleton class is set to null.

• 方案1：可以不传递活动上下文即这个给单子类，而是传递applicationContext()
• 方案2：如果你真的要使用activity上下文，那么当activity被销毁时，确保你传递给单例类的上下文被设置为null

4.内部类引用（4. Inner Class Reference:）

Consider the below example — you have defined a inner class called LeakyClass.java as displayed below and you need to pass the activity in order to redirect to a new activity.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41  public class InnerClassReferenceLeakActivity extends AppCompatActivity { /* * Mistake Number 1: * Never create a static variable of an inner class * Fix I: * private LeakyClass leakyClass; */ private static LeakyClass leakyClass; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); new LeakyClass(this).redirectToSecondScreen(); /* * Inner class is defined here * */ leakyClass = new LeakyClass(this); leakyClass.redirectToSecondScreen(); } /* * Mistake Number 2: * 1. Never create a inner variable of an inner class * 2. Never pass an instance of the activity to the inner class */ private class LeakyClass { private Activity activity; public LeakyClass(Activity activity) { this.activity = activity; } public void redirectToSecondScreen() { this.activity.startActivity(new Intent(activity, SecondActivity.class)); } } } 

How to solve this? Option 1: As mentioned before never create a static variable of an inner class. Option 2: The class should be set to static. Instances of anonymous classes do not hold an implicit reference to their outer class when they are “static”. Option 3: Use a weakReference of any view/activity. Garbage collector can collect an object if only weak references are pointing towards it.

• 方案1：如前所述，千万不要创建一个内类的静态变量。
• 方案2：应该将类设置为静态。匿名类的实例在 “静态 “时不会对其外类持有隐式引用。
• 选项3：使用任何视图/活动的弱引用。如果只有弱引用指向一个对象，垃圾收集器可以收集这个对象。
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50  public class InnerClassReferenceLeakActivity extends AppCompatActivity { /* * Mistake Number 1: * Never create a static variable of an inner class * Fix I: */ private LeakyClass leakyClass; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); new LeakyClass(this).redirectToSecondScreen(); /* * Inner class is defined here * */ leakyClass = new LeakyClass(this); leakyClass.redirectToSecondScreen(); } /* * How to fix the above class: * Fix memory leaks: * Option 1: The class should be set to static * Explanation: Instances of anonymous classes do not hold an implicit reference to their outer class * when they are "static". * * Option 2: Use a weakReference of the textview or any view/activity for that matter * Explanation: Weak References: Garbage collector can collect an object if only weak references * are pointing towards it. * */ private static class LeakyClass { private final WeakReference messageViewReference; public LeakyClass(Activity activity) { this.activity = new WeakReference<>(activity); } public void redirectToSecondScreen() { Activity activity = messageViewReference.get(); if(activity != null) { activity.startActivity(new Intent(activity, SecondActivity.class)); } } } } 

5.匿名内部类引用（5. Anonymous Class Reference:）

This follows the same theory as above. Sample implementation for fixing memory leak is given below:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43  public class AnonymousClassReferenceLeakActivity extends AppCompatActivity { private TextView textView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); textView = findViewById(R.id.activity_text); textView.setText(getString(R.string.text_inner_class_1)); findViewById(R.id.activity_dialog_btn).setVisibility(View.INVISIBLE); /* * Runnable class is defined here * */ new Thread(new LeakyRunnable(textView)).start(); } private static class LeakyRunnable implements Runnable { private final WeakReference messageViewReference; private LeakyRunnable(TextView textView) { this.messageViewReference = new WeakReference<>(textView); } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } TextView textView = messageViewReference.get(); if(textView != null) { textView.setText("Runnable class has completed its work"); } } } } 

Consider the following example — You are using an asyncTask to get a string value which is used to update the textView in OnPostExecute().

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44  public class AsyncTaskReferenceLeakActivity extends AppCompatActivity { private TextView textView; private BackgroundTask backgroundTask; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); /* * Executing AsyncTask here! * */ backgroundTask = new BackgroundTask(textView); backgroundTask.execute(); } /* * Couple of things we should NEVER do here: * Mistake number 1. NEVER reference a class inside the activity. If we definitely need to, we should set the class as static as static inner classes don’t hold * any implicit reference to its parent activity class * Mistake number 2. We should always cancel the asyncTask when activity is destroyed. This is because the asyncTask will still be executing even if the activity * is destroyed. * Mistake number 3. Never use a direct reference of a view from acitivty inside an asynctask. * */ private class BackgroundTask extends AsyncTask { @Override protected String doInBackground(Void... voids) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "The task is completed!"; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); textView.setText(s); } } } 

How to solve this? Option 1: We should always cancel the asyncTask when activity is destroyed. This is because the asyncTask will still be executing even if the activity is destroyed. Option 2: NEVER reference a class inside the activity. If we definitely need to, we should set the class as static as static inner classes don’t hold any implicit reference to its parent activity class. Option 3: Use a weakReference of the textview.

• 方案2：永远不要在activity内部引用类。如果我们一定要这样做，我们应该把类设置为静态的，因为静态的内部类不会对它的父activity类持有任何隐式引用。
• 方案3：使用textview的弱引用。
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65  public class AsyncTaskReferenceLeakActivity extends AppCompatActivity { private TextView textView; private BackgroundTask backgroundTask; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); /* * Executing AsyncTask here! * */ backgroundTask = new BackgroundTask(textView); backgroundTask.execute(); } /* * Fix number 1 * */ private static class BackgroundTask extends AsyncTask { private final WeakReference messageViewReference; private BackgroundTask(TextView textView) { this.messageViewReference = new WeakReference<>(textView); } @Override protected String doInBackground(Void... voids) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "The task is completed!"; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); /* * Fix number 3 * */ TextView textView = messageViewReference.get(); if(textView != null) { textView.setText(s); } } } @Override protected void onDestroy() { super.onDestroy(); /* * Fix number 2 * */ if(backgroundTask != null) { backgroundTask.cancel(true); } } } 

7.Handler引用（7. Handler Reference:）

Consider the following example — You are using a Handler to redirect to a new screen after 5 seconds.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26  public class HandlersReferenceLeakActivity extends AppCompatActivity { private TextView textView; /* * Mistake Number 1 * */ private Handler leakyHandler = new Handler(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); /* * Mistake Number 2 * */ leakyHandler.postDelayed(new Runnable() { @Override public void run() { textView.setText(getString(R.string.text_handler_1)); } }, 5000); } } 

How to solve this? Option 1: NEVER reference a class inside the activity. If we definitely need to, we should set the class as static. This is because when a Handler is instantiated on the main thread, it is associated with the Looper’s message queue. Messages posted to the message queue will hold a reference to the Handler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message. Option 2: Use a weakReference of the Activity.

• 方案一：永远不要在活动里面引用类。如果一定要引用，我们应该将类设置为静态。这是因为当在主线程上实例化一个Handler时，它会与Looper的消息队列相关联。发布到消息队列中的消息将持有对Handler的引用，这样当Looper最终处理消息时，框架可以调用Handler#handleMessage(Message)。
• 方案2：使用Activity的弱引用。
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44  public class HandlersReferenceLeakActivity extends AppCompatActivity { private TextView textView; /* * Fix number I * */ private final LeakyHandler leakyHandler = new LeakyHandler(this); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); leakyHandler.postDelayed(leakyRunnable, 5000); } /* * Fix number II - define as static * */ private static class LeakyHandler extends Handler { /* * Fix number III - Use WeakReferences * */ private WeakReference weakReference; public LeakyHandler(HandlersReferenceLeakActivity activity) { weakReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { HandlersReferenceLeakActivity activity = weakReference.get(); if (activity != null) { activity.textView.setText(activity.getString(R.string.text_handler_2)); } } } private static final Runnable leakyRunnable = new Runnable() { @Override public void run() { /* ... */ } } } 

We can repeat this same mistake again with both the Thread and TimerTask classes.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39  public class ThreadReferenceLeakActivity extends AppCompatActivity { /* * Mistake Number 1: Do not use static variables * */ private static LeakyThread thread; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); createThread(); redirectToNewScreen(); } private void createThread() { thread = new LeakyThread(); thread.start(); } private void redirectToNewScreen() { startActivity(new Intent(this, SecondActivity.class)); } /* * Mistake Number 2: Non-static anonymous classes hold an * implicit reference to their enclosing class. * */ private class LeakyThread extends Thread { @Override public void run() { while (true) { } } } } 

How to solve this? Option 1: Non-static anonymous classes hold an implicit reference to their enclosing class. Option 2: Close thread in activity onDestroy() to avoid thread leak.

• 方案1：非静态匿名类对其包围类持有一个隐式引用。
• 方案2：在onDestroy()活动中关闭线程，避免线程泄漏。
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44  public class ThreadReferenceLeakActivity extends AppCompatActivity { /* * FIX I: make variable non static * */ private LeakyThread leakyThread = new LeakyThread(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); createThread(); redirectToNewScreen(); } private void createThread() { leakyThread.start(); } private void redirectToNewScreen() { startActivity(new Intent(this, SecondActivity.class)); } @Override protected void onDestroy() { super.onDestroy(); // FIX II: kill the thread leakyThread.interrupt(); } /* * Fix III: Make thread static * */ private static class LeakyThread extends Thread { @Override public void run() { while (!isInterrupted()) { } } } } 

The same principle is as Threads can be followed for TimerTask as well. Sample implementation for fixing memory leak is given below:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37  public class TimerTaskReferenceLeakActivity extends Activity { private CountDownTimer countDownTimer; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); startTimer(); } /* * Mistake 1: Cancel Timer is never called * even though activity might be completed * */ public void cancelTimer() { if(countDownTimer != null) countDownTimer.cancel(); } private void startTimer() { countDownTimer = new CountDownTimer(1000, 1000) { @Override public void onTick(long millisUntilFinished) { final int secondsRemaining = (int) (millisUntilFinished / 1000); //update UI } @Override public void onFinish() { //handle onFinish } }; countDownTimer.start(); } } 

How to solve this? Option 1: Cancel timer in activity onDestroy() to avoid memory leak.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45  public class TimerTaskReferenceLeakActivity extends Activity { private CountDownTimer countDownTimer; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); startTimer(); } public void cancelTimer() { if(countDownTimer != null) countDownTimer.cancel(); } private void startTimer() { countDownTimer = new CountDownTimer(1000, 1000) { @Override public void onTick(long millisUntilFinished) { final int secondsRemaining = (int) (millisUntilFinished / 1000); //update UI } @Override public void onFinish() { //handle onFinish } }; countDownTimer.start(); } /* * Fix 1: Cancel Timer when * activity might be completed * */ @Override protected void onDestroy() { super.onDestroy(); cancelTimer(); } } 

So basically to summarise:

1. Use applicationContext() instead of activity context when possible. If you really have to use activity context, then when the activity is destroyed, ensure that the context you passed to the class is set to null.
2. Never use static variables to declare views or activity context.
3. Never reference a class inside the activity. If we need to, we should declare it as static, whether it is a thread or a handler or a timer or an asyncTask.
5. Always use a weakReference of the activity or view when needed.

1. 尽可能使用applicationContext()来代替活动上下文。如果你真的要使用活动上下文，那么当活动被销毁时，确保你传递给类的上下文被设置为null。
2. 千万不要使用静态变量来声明view或activity上下文。