Table of content
- what is memory leak?
- Common causes of memory leaks in Android:
- Reasons of Memory Leak
- So at the end we can say to avoid memory leak we should —
what is memory leak?
When the memory usage grows continuously over time of an Android app, because it fails to release or de-allocate memory that is no longer needed. This can lead to a variety of problems, including ~
- sluggish performance
- reduced responsiveness
- in severe cases, app crashes or the termination of your app by the Android system.
Memory leaks can happen for various reasons in Android apps, and result of resources not being released properly.
Common causes of memory leaks in Android:
- Unclosed Resources: Failing to close or release of resources like database connections, file streams, or network connections can lead to memory leaks.
- Long-lived References: Holding strong references to objects that should be eligible for garbage collection, causing memory leaks. This often occurs when activities, fragments or views are retained the references after they are no longer visible or needed.
- Listener or Callback References: Registering listeners or callbacks without unregistering them can lead to memory leaks.
- Static Variables: Storing data in static variables can cause memory leaks.
- Threads and Handlers: Mishandling threads and handlers can result in memory leaks. If a worker thread holds a reference to the UI thread, it can prevent the UI thread from being garbage collected.
Reasons of Memory Leak
- Broadcast Receiver - can contribute to memory leaks in Android if they are not registered and unregistered properly.
- When a Broadcast Receiver is registered dynamically (i.e., using registerReceiver()), it needs to be unregistered to avoid holding references to the context indefinitely.
- If a receiver is not unregistered, it can lead to memory leaks, as the system won’t be able to release the associated resources.
Example :
public class MainReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Handle the broadcast message
}
}
public class MainActivity extends AppCompatActivity {
private MainReceiver mainReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainReceiver = new MainReceiver();
registerReceiver(mainReceiver, new IntentFilter("my_action"));
}
@Override
protected void onDestroy() {
super.onDestroy();
// Memory leak: The receiver should be unregistered in onDestroy
// unregisterReceiver(myReceiver);
}
}
- To avoid this memory leak, we need to unregister the Broadcast Receiver when it’s no longer needed.
- One can unregister the broadcast receiver in onDestroy() method of the activity or in other appropriate lifecycle methods.
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myReceiver);
}
- Static references to activities or views — in Android can lead to memory leaks if
- those references are not properly managed.
- When a static reference is held to an activity or view, it prevents the associated object from being garbage collected even when it’s no longer in use.
- This can result in increased memory consumption and, in some cases, crashes due to out-of-memory errors.
Example :
public class ReferenceActivity 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 way of coding!");
activity = this;
}
}
- To avoid this memory leak, we should never use static variables, activities or context.
- Singleton Class Reference
public class MSingleton {
private static MainActivity sCurrentActivity;
public static void setCurrentActivity(MyActivity activity) {
sCurrentActivity = activity;
}
public static MainActivity getCurrentActivity() {
return sCurrentActivity;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Setting the current activity in the singleton
MSingleton.setCurrentActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
// Do not rely on garbage collection to clear static references
// This can lead to a memory leak
// MSingleton.setCurrentActivity(null);
}
}
- MSingleton is a class with a static reference sCurrentActivity to an activity (MainActivity).
- In MainActivity, during the onCreate method, the current instance of the activity is set as the current activity in the singleton using MSingleton.setCurrentActivity(this).
- If the onDestroy method of the activity is not properly handling the removal of the activity reference from the singleton (e.g., by calling MSingleton.setCurrentActivity(null)), a memory leak can occur.
To Solve this issue — when using static references, it’s crucial to manage those references properly.
@Override
protected void onDestroy() {
super.onDestroy();
// Properly clear the static reference to avoid memory leak
MSingleton.setCurrentActivity(null);
}
- Timer Task — Using TimerTask in Android can potentially lead to memory leaks if
- the TimerTask holds a reference to an object (such as an Activity or Context) that has a longer lifecycle than the TimerTask itself.
- If the TimerTask outlives the associated Activity, it can prevent the Activity from being garbage collected, causing a memory leak.
Example :
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends AppCompatActivity {
private Timer timer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Creating a TimerTask that holds a reference to the Activity
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
// Some task that refers to the Activity
// For example, trying to access a view in the Activity
TextView textView = findViewById(R.id.textView);
// This reference can cause a memory leak
}
};
// Creating a Timer that schedules the TimerTask to run periodically
timer = new Timer();
timer.schedule(timerTask, 0, 1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
// Cancel the Timer to prevent memory leak
if (timer != null) {
timer.cancel();
}
}
}
- To avoid this memory leak, it’s important to cancel the Timer in the onDestroy method or another appropriate lifecycle method.
- By canceling and purging the timer, the associated TimerTask gets stopped, and its reference to the Activity is released, allowing the Activity to be garbage collected and preventing a memory leak.
@Override
protected void onDestroy() {
super.onDestroy();
// Cancel the Timer to prevent memory leak
if (timer != null) {
timer.cancel();
timer.purge(); // Purge the cancelled tasks from the timer's task queue
}
}
- Threads Reference — can potentially lead to memory leaks if
- the threads are not managed properly.
- One common scenario is when a non-static inner class or an anonymous inner class (e.g., a Runnable or Thread subclass) holds a reference to an Activity or Context.
- If this thread outlives the associated Activity, it can prevent the Activity from being garbage collected, causing a memory leak.
Example :
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Creating a new thread with an anonymous inner class
Thread mThread = new Thread(new Runnable() {
@Override
public void run() {
// Some task that refers to the Activity
// For example, trying to access a view in the Activity
TextView textView = findViewById(R.id.textView);
// This reference can cause a memory leak
}
});
// Starting the thread
mThread.start();
}
}
- The MainActivity class creates a new thread using an anonymous inner class implementing the Runnable interface.
- The run method of the Runnable class contains a reference to the Activity, where it tries to access a TextView. This reference can cause a memory leak if the thread outlives the Activity.
To avoid this issue we can follow the below two ways :
- can use a WeakReference to hold a non-strong reference to the Activity. This way, if the Activity is no longer needed, it can be garbage collected.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final WeakReference<Activity> weakActivity = new WeakReference<>(this);
// Creating a new thread with an anonymous inner class
Thread mThread = new Thread(new Runnable() {
@Override
public void run() {
Activity activity = weakActivity.get();
if (activity != null) {
// Some task that refers to the Activity
// For example, trying to access a view in the Activity
TextView textView = activity.findViewById(R.id.textView);
// Safely use the activity reference
}
}
});
// Starting the thread
mThread.start();
}
}
- Instead of creating threads directly, consider using a Handler or AsyncTask, which are designed to work with the UI thread and can be managed more easily.
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Posting a task to the main thread using a Handler
handler.post(new Runnable() {
@Override
public void run() {
// Some task that refers to the Activity
// For example, trying to access a view in the Activity
TextView textView = findViewById(R.id.textView);
// Safely use the activity reference
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove any remaining callbacks to prevent memory leaks
handler.removeCallbacksAndMessages(null);
}
}
So at the end we can say to avoid memory leak we should —
- Properly closing resources when we are done with them.
- Using weak references when appropriate to avoid preventing garbage collection.
- Being cautious with long-lived references and managing them carefully.
- Unregistering listeners or callbacks when they are no longer needed.
- Avoiding the unnecessary use of static variables.
- Monitoring the app’s memory usage and using profiling tools to identify memory leaks.
By following these best practices and using appropriate debugging and profiling tools, you can help ensure that your Android app’s memory usage is efficient and does not suffer from memory leaks.
Happy learning !