Android Memory Leaks

What is memory leak? What causes it? How to avoid it?

2023-02-14 14:06:16 - Mohamad Abuzaid

What is memory leak?


In Android, a memory leak occurs when an application doesn't release memory that is no longer needed, causing the application to consume more and more memory over time. This can lead to degraded performance and even application crashes due to the system running out of memory. Memory leaks can be caused by a variety of factors, such as holding onto object references that are no longer needed, creating new objects unnecessarily, or not releasing resources properly.

-------------

Possible causes and solutions of memory leak


Below are some practices that can cause memory leak in Android and how we can avoid them.

(a) Keeping references to objects that are no longer needed

Example:

class MyActivity : Activity() {    
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        val button = findViewById<Button>(R.id.my_button)
        button.setOnClickListener {
            val myObject = MyObject()
            // Do something with myObject
        }
    }
}

In this example, If the user clicks the button repeatedly, the clickListener() method will be called again and again, creating more and more instances of MyObject


To avoid this, we should make myObject a local variable instead:

class MyActivity : Activity() {    
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        val myObject = MyObject()
        
        val button = findViewById<Button>(R.id.my_button)
        button.setOnClickListener {
            // Do something with myObject
        }
    }
}


(b) Holding onto Activity/Fragment instances

Example:

class MyObject {
    private var activity: Activity? = null
    
    fun setActivity(activity: Activity) {
        this.activity = activity
    }
}

In this example, MyObject holds onto a reference to an Activity instance, which can cause a memory leak if the Activity is destroyed but MyObject is still holding onto the reference.


To fix this, we can use a WeakReference instead:

class MyObject {
    private var activityRef: WeakReference<Activity>? = null
    
    fun setActivity(activity: Activity) {
        this.activityRef = WeakReference(activity)
    }
}


(c) Registering callbacks without unregistering them

Example:

class MyService : Service() {
    private val myReceiver = MyReceiver()
    
    override fun onCreate() {
        super.onCreate()
        registerReceiver(myReceiver, IntentFilter("my_action"))
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // Oops, we forgot to unregister myReceiver!
    }
}

In this example, MyService registers a BroadcastReceiver instance (myReceiver) in the onCreate() method, but it forgets to unregister it in the onDestroy() method. This can cause a memory leak, as the BroadcastReceiver will still be active even after the Service is destroyed.


To fix this, we need to unregister the BroadcastReceiver in onDestroy():

class MyService : Service() {
    private val myReceiver = MyReceiver()
    
    override fun onCreate() {
        super.onCreate()
        registerReceiver(myReceiver, IntentFilter("my_action"))
    }
    
    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(myReceiver)
    }
}


(d) Using static variables

Example:

class MyObject {
    companion object {
        var myValue: Int = 0
    }
}

In this example, MyObject has a companion object that contains a static variable myValue. This variable will remain in memory for the lifetime of the application, even if no instances of MyObject are in use.


To avoid this, we can make myValue an instance variable instead:

class MyObject {
    var myValue: Int = 0
}


More Posts