Featured image of post IT Doctor Diagnoses Common Memory Leak Cases in Android

IT Doctor Diagnoses Common Memory Leak Cases in Android

In the previous article about Stack and Heap Memory in Java, I mentioned Memory Leaks as a cause of java.lang.OutOfMemoryError. Today, let's dive into specific examples that cause Memory Leaks to learn how to prevent and fix them.

Photo by Patrick Hendry on Unsplash

In the previous article Stack and Heap Memory in Java, I mentioned Memory Leaks as a cause of java.lang.OutOfMemoryError. Today, let’s dive into specific examples that cause Memory Leaks to learn how to prevent and fix them. Let’s get started and diagnose each case like an IT doctor!

Static Reference to Context

Sometimes, by mistake, you might declare an Activity or Context as a static variable. This keeps a reference to the Activity or Context in that static variable, preventing the Garbage Collector from reclaiming memory and leading to a memory leak. You can read more about how the Garbage Collector works in this article.

1
2
3
4
5
6
7
8
9
class MemoryLeakExample {
    companion object {
        var context: Context? = null  // Static reference to Context
    }

    fun initialize(context: Context) {
        this.context = context
    }
}
1
2
3
object MySingleton {
    var context: Context? = null
}

To fix this, use applicationContext instead of the Activity context.

1
2
3
4
5
6
7
8
9
class MemorySafeExample {
    companion object {
        var context: Context? = null
    }

    fun initialize(context: Context) {
        this.context = context.applicationContext  // Use applicationContext instead of Activity context
    }
}

Or, if you must store context in a singleton/static, use WeakReference:

1
2
3
4
5
6
7
8
9
object MySingleton {
    private var weakContext: WeakReference<Context>? = null

    fun setContext(context: Context) {
        weakContext = WeakReference(context.applicationContext)
    }

    fun getContext(): Context? = weakContext?.get()
}

Inner Class (Non-Static)

Inner classes always carry an implicit reference to the outer class. So, if a Handler, Runnable, etc. in an inner class continues to exist after the Activity is destroyed, it can cause a memory leak.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class MainActivity : AppCompatActivity() {
    private val handler = Handler(Looper.getMainLooper())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        handler.postDelayed({
            // Code that refers to activity
        }, 1000)
    }
}

To fix this, use WeakReference or a static inner class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class MainActivity : AppCompatActivity() {
    private val handler = Handler(Looper.getMainLooper())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val weakActivity = WeakReference(this)
        handler.postDelayed({
            weakActivity.get()?.let {
                // Safe usage of activity
            }
        }, 1000)
    }
}

Forgetting to Unregister Listener

Forgetting to unregister a listener or BroadcastReceiver after an Activity/Fragment is destroyed can prevent the activity from being reclaimed by the Garbage Collector.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class MainActivity : AppCompatActivity() {
    private lateinit var receiver: BroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        receiver = BroadcastReceiver { context, intent -> }
        registerReceiver(receiver, IntentFilter("com.example.MY_ACTION"))
    }

    // Forgot to unregister receiver
}

Always remember to unregister listeners/receivers in onDestroy():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class MainActivity : AppCompatActivity() {
    private lateinit var receiver: BroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        receiver = BroadcastReceiver { context, intent -> }
        registerReceiver(receiver, IntentFilter("com.example.MY_ACTION"))
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(receiver)  // Unregister when activity is destroyed
    }
}

Long-Running Async Task or Thread

Background tasks like coroutine, thread, or Runnable that continue running after the Activity is destroyed and still hold a reference to the old Activity are also a common cause of memory leaks.

Some solutions for this case:

  • Cancel coroutine/async tasks in onDestroy()
  • Use lifecycleScope/viewModelScope to automatically cancel tasks when the lifecycle ends

Conclusion

Besides remembering and applying the best practices above, you can also use tools like LeakCanary or Memory Profiler in Android Studio to monitor and debug memory leaks. Understanding and avoiding memory leaks will help your Android app save RAM and run more smoothly.

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy