DEV Community

Papon Ahasan
Papon Ahasan

Posted on • Edited on

Memory Leaks

🧠 When Can Memory Leaks Happen?

Memory leaks in Android typically happen when an object that holds a reference to a Context (especially Activity) outlives its lifecycle, preventing the system from garbage collecting the Activity.

🧩 Types of Context and When to Use Which

✅ đŸšĢ Avoid Memory Leaks with These Tips

✅ 1. Use applicationContext when:

  • You don’t need to access UI

  • You're working in long-lived classes: e.g., repositories, databases, analytics trackers

val context = context.applicationContext 
Enter fullscreen mode Exit fullscreen mode

✅ 2. Avoid Anonymous Inner Classes in Activities

Use static classes or top-level classes to avoid holding implicit references to Activities.

✅ 3. Clear Resources in Lifecycle

Always clear:

  • Observers (LiveData, BroadcastReceiver)

  • Coroutine scopes (Job.cancel())

  • Ad SDKs (nativeAd.destroy())

✅ 4. Don’t Hold Context in Singletons

If you must, store applicationContext, not Activity.

class MySingleton private constructor(private val context: Context) { companion object { fun init(appContext: Context): MySingleton { return MySingleton(appContext.applicationContext) } } } 
Enter fullscreen mode Exit fullscreen mode
  1. Holding a reference to an Activity or View in a Singleton ❌ Problem:
object MyManager { var context: Context? = null // holding activity context } 
Enter fullscreen mode Exit fullscreen mode

📌 Real-World Example:

You create a singleton for analytics or ads that stores an Activity context to show a Toast or dialog — but forget to clear it.

✅ Solution:

Always use context.applicationContext in singletons.

context = context.applicationContext 
Enter fullscreen mode Exit fullscreen mode
  1. Inner classes or anonymous classes (e.g. Runnable, Listener) holding implicit reference to outer Activity

❌ Problem:

class MyActivity : AppCompatActivity() { private val handler = Handler(Looper.getMainLooper()) fun start() { handler.postDelayed({ // 'this' refers to Activity, leak if Activity is destroyed }, 10000) } } 
Enter fullscreen mode Exit fullscreen mode

✅ Solution:

Make the runnable a static class or cancel delayed tasks in onDestroy().

override fun onDestroy() { super.onDestroy() handler.removeCallbacksAndMessages(null) } 
Enter fullscreen mode Exit fullscreen mode
  1. Long-running background tasks (Coroutines, AsyncTask, Threads) holding context

❌ Problem:

You start a coroutine in a ViewModel or plain class and hold an Activity context.

fun loadData(context: Context) { CoroutineScope(Dispatchers.IO).launch { // Holding context across configuration change or after activity is gone val db = DB.getInstance(context) } } 
Enter fullscreen mode Exit fullscreen mode

✅ Solution:

  • Pass applicationContext if needed.
  • Cancel coroutine on lifecycle.
fun loadData(appContext: Context) { CoroutineScope(Dispatchers.IO).launch { val db = DB.getInstance(appContext) } } 
Enter fullscreen mode Exit fullscreen mode
  1. LiveData or Flow observing with lifecycle issues

❌ Problem:

You observe LiveData from ViewModel in a Context-based class (like a custom manager), not tied to lifecycle.

viewModel.data.observeForever { // Forever means it never stops -> leak } 
Enter fullscreen mode Exit fullscreen mode

✅ Solution:

Always observe with LifecycleOwner (Activity or Fragment) unless you manually remove observers.

viewModel.data.observe(viewLifecycleOwner) { ... } 
Enter fullscreen mode Exit fullscreen mode
  1. Dialogs or Toasts shown after Activity is destroyed ❌ Problem:
fun showDialog(context: Context) { AlertDialog.Builder(context) .setMessage("Hello") .show() } 
Enter fullscreen mode Exit fullscreen mode

If context is an Activity and it’s already finishing, the window leaks.
✅ Solution:

Check context:

if (context is Activity && !context.isFinishing) { AlertDialog.Builder(context) .setMessage("Safe") .show() } 
Enter fullscreen mode Exit fullscreen mode
  1. Static Views or holding binding in fragments after view is destroyed ❌ Problem:
class MyFragment : Fragment() { private lateinit var binding: FragmentMyBinding override fun onDestroyView() { super.onDestroyView() // Forget to clear binding -> view leak } } 
Enter fullscreen mode Exit fullscreen mode

✅ Solution:

Clear binding in onDestroyView():

override fun onDestroyView() { super.onDestroyView() _binding = null } 
Enter fullscreen mode Exit fullscreen mode
  1. Custom callbacks or listeners not removed ❌ Problem:
someView.setOnClickListener { // Holds reference to outer Activity } 
Enter fullscreen mode Exit fullscreen mode

If the view lives longer (e.g., part of an SDK), it keeps the activity alive.
✅ Solution:

Remove listeners:

override fun onDestroy() { someView.setOnClickListener(null) } 
Enter fullscreen mode Exit fullscreen mode
class MySingleton private constructor(private val context: Context) { companion object { @Volatile private var INSTANCE: MySingleton? = null fun getInstance(appContext: Context): MySingleton { return INSTANCE ?: synchronized(this) { INSTANCE ?: MySingleton(appContext.applicationContext).also { INSTANCE = it } } } } // Example usage of application context fun doSomethingGlobal() { Toast.makeText(context, "Doing something!", Toast.LENGTH_SHORT).show() } } 
Enter fullscreen mode Exit fullscreen mode
private var weakActivity: WeakReference<Activity>? = null fun bindActivity(activity: Activity) { weakActivity = WeakReference(activity) } fun doSomething() { val activity = weakActivity?.get() ?: return // Use it safely } 
Enter fullscreen mode Exit fullscreen mode

❌ Problem: 🔴 Without Job — āϏāĻŽāĻ¸ā§āϝāĻž:

👉 āϝāĻĻāĻŋ user ā§Ē āϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡āϰ āĻŽāĻ§ā§āϝ⧇ āĻŦāĻžāϰāĻŦāĻžāϰ call āĻ•āϰ⧇, āϏāĻŦ coroutine queue āϤ⧇ āĻĸ⧁āϕ⧇ āϝāĻžāĻŦ⧇ → multiple popups → đŸ˜ĩ UI bug!

lifecycleScope.launch { delay(4000) getDrugDetailPopupImage(genericId) } 
Enter fullscreen mode Exit fullscreen mode

đŸŸĸ With Job — Smart & Controlled: 👉 āĻāĻ•āĻŦāĻžāϰ⧇ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻāĻ•āϟāĻž popup āϚāϞāĻŦ⧇āĨ¤ āύāϤ⧁āύ āĻ•āĻžāϜ āĻāϞ⧇ āĻĒ⧁āϰāύ⧋āϟāĻž āϕ⧇āĻŸā§‡ āϝāĻžāĻŦ⧇āĨ¤

private var popupJob: Job? = null popupJob?.cancel() // āĻĒ⧁āϰāύ⧋ āϚāϞāĻŽāĻžāύ coroutine āĻŦāĻ¨ā§āϧ popupJob = lifecycleScope.launch { delay(4000) getDrugDetailPopupImage(genericId) } 
Enter fullscreen mode Exit fullscreen mode

📌 āĻāϰ āωāĻĻā§āĻĻ⧇āĻļā§āϝ:

  • āφāϗ⧇āϰ popup coroutine āϚāϞāĻŽāĻžāύ āĻĨāĻžāĻ•āϞ⧇ āϤāĻž cancel āĻ•āϰ⧇ āĻĢ⧇āϞāĻž
  • āύāϤ⧁āύ coroutine āĻĻāĻŋā§Ÿā§‡ popup image delayed show āĻ•āϰāĻž
  • āĻāĻ•āχ āϏāĻŽā§Ÿ āĻāĻ•āϟāĻŋāϰ āĻŦ⧇āĻļāĻŋ popup āύāĻž āĻĻ⧇āĻ–āĻžāύ⧋
  • 🧠 Memory leak āϰ⧋āϧ āύāĻž āϚāĻžāχāϤ⧇āĻ“ āϚāϞāϤ⧇ āĻĨāĻžāĻ•āĻž coroutine āĻŦāĻ¨ā§āϧ āĻ•āϰ⧇ āĻĻ⧇

āĻŽā§‡āĻŽā§‹āϰāĻŋ āϞāĻŋāĻ• āϕ⧀?

āĻŽā§‡āĻŽā§‹āϰāĻŋ āϞāĻŋāĻ• āϤāĻ–āύ āĻšā§Ÿ, āϝāĻ–āύ āφāĻĒāύāĻžāϰ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻ…āĻŦāĻœā§‡āĻ•ā§āϟāϗ⧁āϞ⧋ āĻ—āĻžāϰāĻŦ⧇āϜ āĻ•āĻžāϞ⧇āĻ•ā§āϟāϰ (Garbage Collector) āϰāĻŋāĻŽā§āĻ­ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇ āύāĻž, āĻ•āĻžāϰāĻŖ āĻāĻ–āύāĻ“ āϕ⧇āω āϏ⧇āχ āĻ…āĻŦāĻœā§‡āĻ•ā§āĻŸā§‡āϰ āϰ⧇āĻĢāĻžāϰ⧇āĻ¨ā§āϏ āϧāϰ⧇ āϰ⧇āϖ⧇āϛ⧇ — āϝāĻĻāĻŋāĻ“ āϏ⧇āχ āĻ…āĻŦāĻœā§‡āĻ•ā§āϟ āĻĻāϰāĻ•āĻžāϰ āύ⧇āχāĨ¤ āĻĢāϞ⧇ āĻ…ā§āϝāĻžāĻĒ⧇āϰ RAM āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻŦāĻžā§œā§‡, āĻĒāĻžāϰāĻĢāϰāĻŽā§āϝāĻžāĻ¨ā§āϏ āĻ•āĻŽā§‡, āĻāĻŽāύāĻ•āĻŋ āĻ…ā§āϝāĻžāĻĒ āĻ•ā§āĻ°ā§āϝāĻžāĻļāĻ“ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤

LeakCanary āĻšāϞ Square āϕ⧋āĻŽā§āĻĒāĻžāύāĻŋāϰ āĻŦāĻžāύāĻžāύ⧋ āĻāĻ•āϟāĻž open-source Android library āϝāĻž āĻŽā§‡āĻŽā§‹āϰāĻŋ āϞāĻŋāĻ• āĻ…āĻŸā§‹ āĻĄāĻŋāĻŸā§‡āĻ•ā§āϟ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇ āĻĄā§‡āϭ⧇āϞāĻĒāĻŽā§‡āĻ¨ā§āϟ āĻŦāĻŋāĻ˛ā§āĻĄā§‡āĨ¤

// build.gradle (app) dependencies { debugImplementation "com.squareup.leakcanary:leakcanary-android:2.13" } 
Enter fullscreen mode Exit fullscreen mode

Leak Report āĻĻ⧇āĻ–āϤ⧇ āϕ⧇āĻŽāύ āĻšā§Ÿ?

// âžĄī¸ āĻāχ report āĻŦāϞāϛ⧇ MyActivity destroy āĻšāĻ“ā§ŸāĻžāϰ āĻĒāϰ⧇āĻ“ mContext āϰ⧇āĻĢāĻžāϰ⧇āĻ¨ā§āϏ⧇ āĻĨāĻžāĻ•āĻžāϰ āĻ•āĻžāϰāϪ⧇ release āĻšā§ŸāύāĻŋāĨ¤ ActivityLeakActivity has leaked: â†ŗ xyz.MyActivity instance | mContext → xyz.MyActivity | mDestroyed → true 
Enter fullscreen mode Exit fullscreen mode

Tip1: Leak Canary Debug Log:

D LeakCanary: Watching instance of xyz.MyActivity (Activity) with key XYZ123... 
Enter fullscreen mode Exit fullscreen mode
â”Ŧ ├─ com.example.MyActivity │ Leaking: YES │ Retaining 1.2 MB in 10060 bytes │ ↓ MyActivity.someStaticReference │ ~~~~~~~~~~~~~ ├─ android.widget.SomeView │ ↓ View.mContext │ ~~~~~~~~ ... 
Enter fullscreen mode Exit fullscreen mode


- MyFragment.binding null āĻ•āϰāĻž āĻšā§ŸāύāĻŋ, āϤāĻžāχ View āĻāϰ āĻŽāĻ§ā§āϝ⧇ āĻĨāĻžāĻ•āĻž context (Activity) leak āĻšā§Ÿā§‡āϛ⧇āĨ¤ ✅ āϏāĻŽāĻžāϧāĻžāύ: onDestroyView() āĻ binding = null āĻ•āϰ⧇ āĻĻāĻŋāύāĨ¤ â”Ŧ ├─ MyFragment │ Leaking: YES │ ↓ MyFragment.binding │ ~~~~~~~ ├─ MyFragmentBinding │ ↓ binding.textView │ ~~~~~~~~ ├─ TextView │ ↓ View.mContext │ ~~~~~~~ 
Enter fullscreen mode Exit fullscreen mode

āĻĒā§āϰāϤāĻŋāϟāĻŋ āϞāĻžāχāύ āĻĻ⧇āϖ⧁āύ āϝ⧇āϟāĻžāϤ⧇ ↓ (down arrow) āφāϛ⧇, LeakCanary āĻāχ arrows āĻĻāĻŋā§Ÿā§‡ āĻŦā§‹āĻāĻžā§Ÿ āĻ•āĻžāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϰ⧇āĻĢāĻžāϰ⧇āĻ¨ā§āϏ āϧāϰ⧇ āφāϛ⧇:

StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() .detectDiskReads() // āĻĄāĻŋāĻ¸ā§āĻ• āĻĨ⧇āϕ⧇ āĻĄāĻžāϟāĻž āĻĒ⧜āĻž āĻšāϞ⧇ āϏāϤāĻ°ā§āĻ• āĻ•āϰāĻŦ⧇ .detectDiskWrites() // āĻĄāĻŋāĻ¸ā§āϕ⧇ āϞ⧇āĻ–āĻž āĻšāϞ⧇ āϏāϤāĻ°ā§āĻ• āĻ•āϰāĻŦ⧇ .detectNetwork() // āύ⧇āϟāĻ“ā§ŸāĻžāĻ°ā§āĻ• āĻ•āϞ āĻšāϞ⧇ āϏāϤāĻ°ā§āĻ• āĻ•āϰāĻŦ⧇ .penaltyLog() // āĻāϏāĻŦ āϘāϟāύāĻžāϰ āϏāĻŽā§Ÿ āϞāĻ— āĻ•āϰāĻŦ⧇ .build() ) StrictMode.setVmPolicy( StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() // āϝ⧇āϏāĻŦ Sqlite āĻ…āĻŦāĻœā§‡āĻ•ā§āϟ āĻŦāĻ¨ā§āϧ āĻšā§ŸāύāĻŋ, āϏ⧇āϗ⧁āϞ⧋ āϚāĻŋāύāĻŦ⧇ .detectLeakedClosableObjects() // āϝ⧇āϏāĻŦ āĻ•ā§āϞ⧋āĻœā§‡āĻŦāϞ āĻ…āĻŦāĻœā§‡āĻ•ā§āϟ (āϝ⧇āĻŽāύ āĻĢāĻžāχāϞ, āĻ¸ā§āĻŸā§āϰ⧀āĻŽ) āĻŦāĻ¨ā§āϧ āĻšā§ŸāύāĻŋ āϏ⧇āϗ⧁āϞ⧋ āϚāĻŋāύāĻŦ⧇ .penaltyLog() // āĻāϏāĻŦ āχāĻ¸ā§āϝ⧁ āĻšāϞ⧇ āϞāĻ— āĻ•āϰāĻŦ⧇ .build() ) - āĻŽā§‡āχāύ āĻĨā§āϰ⧇āĻĄā§‡ āĻĄāĻŋāĻ¸ā§āĻ• āĻŦāĻž āύ⧇āϟāĻ“ā§ŸāĻžāĻ°ā§āĻ• āĻ…āĻĒāĻžāϰ⧇āĻļāύ āĻšāϞ⧇ āϤ⧋āĻŽāĻžāϕ⧇ āϜāĻžāύāĻžāĻŦ⧇ - āϝ⧇āϏāĻŦ āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻŦāĻž āĻĢāĻžāχāϞ āϰāĻŋāϏ⧋āĻ°ā§āϏ āϤ⧁āĻŽāĻŋ āϭ⧁āϞ⧇ āĻŦāĻ¨ā§āϧ āĻ•āϰ⧋ āύāĻŋ, āϏ⧇āϗ⧁āϞ⧋āĻ“ āϧāϰāĻŦ⧇ - āϏāĻŦāχ āϞāĻ— āĻ•āϰ⧇ āĻĄā§‡āϭ⧇āϞāĻĒāĻžāϰāϕ⧇ āχāĻ¸ā§āϝ⧁ āϏāĻŽā§āĻĒāĻ°ā§āϕ⧇ āϏāĻšā§‡āϤāύ āĻ•āϰāĻŦ⧇ āϕ⧇āύ āĻĻāϰāĻ•āĻžāϰ? āĻ…ā§āϝāĻžāĻĒ āĻ āĻŋāĻ•āĻ āĻžāĻ• āĻāĻŦāĻ‚ āĻĻā§āϰ⧁āϤ āĻ•āĻžāϜ āĻ•āϰ⧁āĻ• āφāϰ āχāωāϜāĻžāϰ āĻ­āĻžāϞ⧋ āĻ…āĻ­āĻŋāĻœā§āĻžāϤāĻž āĻĒāĻžā§Ÿ, āϏ⧇āϜāĻ¨ā§āϝ āϝ⧇āϏāĻŦ āĻ•āĻžāϜ āĻŽā§‡āχāύ āĻĨā§āϰ⧇āĻĄ āĻŦā§āϞāĻ• āĻ•āϰ⧇ āϏ⧇āϗ⧁āϞ⧋ āϖ⧁āρāĻœā§‡ āĻŦ⧇āϰ āĻ•āϰāĻž āϖ⧁āĻŦ āϜāϰ⧁āϰāĻŋāĨ¤ StrictMode āϏ⧇āχ āĻ•āĻžāĻœā§‡ āϏāĻžāĻšāĻžāĻ¯ā§āϝ āĻ•āϰ⧇āĨ¤ 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)