Understanding Handler Memory Leaks in Android
Memory Leak Definition
Memory leak occurs when dynamically allocated heap memory cannot be freed despite no longer being needed, causing resource waste. This leads to degraded performance and potential system crashes.
Problematic Code Example
public class HomeActivity extends AppCompatActivity {
private Handler handler = new Handler();
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
textView = findViewById(R.id.text_view);
handler.postDelayed(() -> textView.setText("updated"), 2000);
}
}
Root Cause Analysis
The code above creates a Handler using a non-static inner class. In Java, non-static inner classes implicitly hold a strong reference to their enclosing class. When postDelayed() is called, the Runnable message is placed into the MessageQueue, where Looper continues processing it.
When the Activity attempts to finish, the following conditions create a leak:
- Looper's lifecyccle exceeds Activity's lifecycle: The Looper continues running and processing messages even after the Activity calls
onDestroy() - Handler holds Activity reference: The inner Handler class maintains a reference to the enclosing Activity
- MessageQueue holds pending messages: Unprocessed or delayed messages prevent garbage collection
This combination means the Activity cannot be reclaimed by the garbage collector, resultinng in a memory leak.
Interestingly, other inner classes like TextView also hold Activity references but don't cause leaks. The critical difference is thier lifecycle - they are typically garbage collected when the Activity is destroyed, whereas the Handler's message-processing mechanism outlives the Activity.
Solution 1: Static Inner Class with Weak Reference
public class ProfileActivity extends AppCompatActivity {
private static class AsyncHandler extends Handler {
private final WeakReference<ProfileActivity> activityRef;
AsyncHandler(ProfileActivity activity) {
activityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
ProfileActivity activity = activityRef.get();
if (activity != null) {
// Handle message safely
}
}
}
private final AsyncHandler handler = new AsyncHandler(this);
private static final Runnable delayedTask = () -> { };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler.postDelayed(delayedTask, 60000);
}
}
Using a static inner class eliminates the implicit reference to the outer class. The WeakReference allows the Activity to be garbage collected even if the Handler still exists. WeakReferences have short lifecycles and are eligible for collection when memory is low.
Solution 2: Clear Message Queue on Destroy
@Override
protected void onDestroy() {
super.onDestroy();
if (handler != null) {
handler.removeCallbacksAndMessages(null);
handler = null;
}
}
Calling removeCallbacksAndMessages(null) removes all pending callbacks and messages from the queue, ensuring the Handler no longer references the Activity.