Practical Guide To Solve OutOfMemoryError in Android Application

Practical Guide To Solve OutOfMemoryError in Android Application

OutOfMemoryError or simply OOM is something that every Android developer must have encountered with. If you haven't seen any OOM in your Android application, then you are going to have one in future. The OutOfMemoryError comes in Android due to memory leaks. So, in order to remove the OutOfMemoryError, you need to remove memory leaks from your Android application.

In this blog, we will solve the OutOfMemoryError in Android with the help of some practical examples. So, let's get started with the basics of Memory Leak in Android.

What is a Memory Leak?

When you run some application on the Android device, then the Android system will provide some memory to your application for its working. All the variable creation, function creation, activity creation, etc takes place in that memory only.

For example, if the Android System allocates 100MB for your application, then your application can use 100MB max at one particular time. With due course of time, if space allocated to the application gets reduced or very few amounts of space is left, then the Garbage Collector(GC) will release the memory that is being held by those variables and activities that are of no use. In this way, the application will again gain some space.

NOTE: The Garbage Collector performs the task of releasing the memory automatically and you don't have to do anything for this.

But sometimes, when you write the code in a bad manner such that your code holds the references of objects that are not required anymore, then in this situation, the Garbage Collector is unable to release the unused space and no space will be left for the application for its further working. This is called a Memory Leak.

Due to the phenomenon of Memory Leak in Android, we encounter with the OutOfMemoryError in Android because your code is holding the references of the objects that are not required anymore and the Garbage Collector is unable to perform its job and your application uses all the space allocated to it by the Android System and is demanding for more.

What may be the reasons for OutOfMemoryError?

There are various reasons that can lead to OutOfMemoryError in Android. Some of the common reason for Memory Leaks that results in OutOfMemoryError are:

  • Use of Static view/context/activity
  • Register and unregister listeners
  • Non-Static inner class
  • Wrong use of getContext() and getApplicationContext()

Let's learn one by one in detail.

Use of static views/context/activity

If you are using some static views/context/activity then you are going to have OutOfMemoryError(if your activities are dealing with lots of space). This is because the view or the context or the activity will be held by the application until the application is alive and due to this the memory taken by these will not be released by the Garbage Collector.

For example, In your application, you are having 4 activities i.e. A, B, C, and D. Activity "A" is your main activity and you can open the activity B, C, and D from A. Now, suppose the activities B, C, and D are holding static reference to its context and each activity is using 2MB and total memory allocated to the application by the Android System is 4MB. So, when activity "B" will be started then the memory used by the application will be 2MB. Now, come back to the activity "A" and start activity "C". Now, the memory used by the application will be 4MB(2MB for Activity B and 2MB for Activity C). Again come back to activity "A" and start activity "D". Now, your app will require 6MB(2MB for B, 2MB for C and 2MB for D), but the allocated memory is 4MB. So, here while opening the activity "D", you will get OutOfMemoryError because the allotted memory is 4MB and you are demanding for 6MB.

Let's look at one practical implementation. Here in this example, I am using Bitmap. By using more than 5 images of size 1MB each, my application will encounter OutOfMemoryError on my mobile device. In your case, the number of images that can result in OutOfMemoryError can be more than or less than 5. You can find the limit by adding images to the BitmapArray one by one and whenever you get the OOM, that is the limit of your device.

So, my application is having one MainActivity and from this activity, I can go to "ActivityB" and "ActivityC". Both these activities i.e. "ActivityB" and "ActivityC" are having static reference of their context. So, space used by these activities will not be deleted by Garbage Collector.

Following is the code for ActivityB:

class ActivityB : AppCompatActivity() {
    // static context
    companion object {
        lateinit var context: Context
    }
    val bitmapArray = ArrayList<Bitmap>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_b)
        context = this
        Thread(Runnable {
            try {
                // adding 3 images to bitmapArray
                val bitmap1: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1)
                val bitmap2: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image2)
                val bitmap3: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image3)
                bitmapArray.add(bitmap1)
                bitmapArray.add(bitmap2)
                bitmapArray.add(bitmap3)
            } catch (e: Exception){
                Logger.getLogger(ActivityB::class.java.name).warning(e.toString())
            }
        }).start()
    }
}

Following is the code for the ActivityC:

class ActivityC : AppCompatActivity() {
    companion object {
        lateinit var context: Context
    }
    val bitmapArray = ArrayList<Bitmap>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_c)
        context = this
        Thread(Runnable {
            try {

                val bitmap1: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1)
                val bitmap2: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image2)
                val bitmap3: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image3)
                bitmapArray.add(bitmap1)
                bitmapArray.add(bitmap2)
                bitmapArray.add(bitmap3)
            } catch (e: Exception){
                Logger.getLogger(ActivityC::class.java.name).warning(e.toString())
            }

        }).start()
    }
}

So, if you launch the ActivityB from MainActivity, then there will no OutOfMemoryError because the limit for my mobile device is 5 images and in the ActivityB, I am using only 3 images.

Now, come back to the MainActivity and launch ActivityC and you will get OutOfMemoryError because here in the ActivityC, we are using 3 images and since we are using the static context in both ActivityB and ActivityC, the reference of ActivityB is still with us and overall we have 6 images(3 from ActivityB and 3 from ActivityC) and the limit for my mobile device is 5 images. So, OutOfMemoryError will occur.

To remove this error, make the context or view or activity, Non-Static.

class ActivityB : AppCompatActivity() {
    
    lateinit var context: Context
    val bitmapArray = ArrayList<Bitmap>()

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

Register and unregister listeners

In Android, we use various kind of listeners to listen to the changes like the location change. So, never forgot to unregister your listener when you are done using that listener. If you are not unregistering the listeners, then the listener will be present in the background and the space taken by the listener will not be deleted by the Garbage Collector even when the listener is of no use. So, it is better to unregister the listener if not in use. Let's look at one practical example.

In this example, we are having one Singleton class named "SomeListener" that will be having two functions i.e. register() and unregister(). The register() function is used to add the activity in an activity array and the unregister() function is used to remove the activity from the activity array. Following is the code for the Singleton class:

object SomeListener {
    private val activityArray = ArrayList<Activity>()

    fun register(activity: Activity) {
        activityArray.add(activity)
    }
    fun unregister(activity: Activity) {
        activityArray.remove(activity)
    }
}

In my mobile device, if I am adding more than 5 images, then the application will give OutOfMemoryError. So, in this example, we will be having three activities i.e. MainActivity, ActivityB, and ActivityC. The ActivityB and ActivityC will be called from MainActivity and in the onCreate() function of both these activities, we will call the register() method of the Singleton class to add the activities in the activity array.

Following is the code of ActivityB:

class ActivityB : AppCompatActivity() {
    
    val bitmapArray = ArrayList<Bitmap>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_b)
        Thread(Runnable {
            try {
                // adding 3 images to bitmapArray
                val bitmap1: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1)
                val bitmap2: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image2)
                val bitmap3: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image3)
                bitmapArray.add(bitmap1)
                bitmapArray.add(bitmap2)
                bitmapArray.add(bitmap3)
            } catch (e: Exception){
                Logger.getLogger(ActivityB::class.java.name).warning(e.toString())
            }
        }).start()
        // calling the register() function
        SomeListener.register(this)
    }
}

Following is the code of ActivityC:

class ActivityC : AppCompatActivity() {
    
    val bitmapArray = ArrayList<Bitmap>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_c)
        Thread(Runnable {
            try {
                // adding 3 images to bitmapArray
                val bitmap1: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1)
                val bitmap2: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image2)
                val bitmap3: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image3)
                bitmapArray.add(bitmap1)
                bitmapArray.add(bitmap2)
                bitmapArray.add(bitmap3)
            } catch (e: Exception){
                Logger.getLogger(ActivityC::class.java.name).warning(e.toString())
            }
        }).start()
        // calling the register() function
        SomeListener.register(this)
    }
}

If we call the ActivityB from MainActivity, then the reference of 3 images will be stored(as we are storing in the Singleton class). Here, no OutOfMemoryError will be shown because our limit is of 5 images. Now, if we come back to the MainActivity and call the ActivityC, then we will get OutOfMemoryError because initially, we are having reference to 3 images and when ActivityC is launched, then 3 more images came and overall we have reference to 6 images(as we are using the same Singleton class) and our limit is of 5 images. So, we will encounter MemoryOutOfBoundError.

To remove this error, you have to deregister the listener when the activity is closed or of no use. So, add the below lines of code to unregister the listener when the activity is stopped:

override fun onStop() {
    SomeListener.unregister(this)
    super.onStop()
}

The nested class must be Static:

If you are having some nested class in the application, then make it a static class because the static class does not need the outer class implicit reference. So, if you are using the inner class as non-static then it makes the outer class alive until the application is alive. So, this can lead to OutOfMemoryError if your class is using a lot of memory. So, it is better to make the inner class Static.

In Java, you have to make the inner class static by your own but in Kotlin, the inner class is by default static in nature. So, you need not worry about static inner class in Kotlin.

Wrong use of getContext() and getApplicationContext() in third party libraries

We use many third-party libraries in our application and most of them use Singleton class. So, if you are passing some context to a third-party library and that third-party library is beyond the scope of the current activity, then use getApplicationContext() and not getContext().

Generally, we do the below thing in our application:

SomeThirdPartyLibrary.initialize(this)

Here, initialize is some static function in that library and it uses the context something like this:

SomeThirdPartyLibrary {
  object companion {
    initialize(Content context) {
       this.context = context.getApplicationContext()
    }
  }
}

But some of the libraries don't use the above notation. So, in that case, it will use the context of the current activity and due to this, the reference of the current activity will be held until the application is alive and this can result in OutOfMemoryError(as the initialize function is static).

So, it is better to use the getApplicationContext() in your code explicitly and don't depend on the third-party libraries to do that.

These are some of the techniques that can be used to remove OutOfMemoryError from our application. It is better to write the code that doesn't give any OutOfMemoryError. Still, if your project is very big and you are getting it harder to find the class that is responsible for OutOfMemoryError, then you can use the memory profiler of Android Studio to find the classes responsible for OutOfMemoryError.

Learn how to use Android Studio Memory Profiler

Also, there is one library called LeakCanary, that tells you about the class that is responsible for memory leaks in your Android application(if any).

Learn more about LearCanary

Conclusion

In this blog, we learned how to remove OutOfMemoryError in Android. This can be removed by using a non-static reference of views/context/activity, by registering and unregistering the listeners, by using a static inner class. So, in future, if you are writing some code in Android, then I am sure that you are going to use these to avoid OutOfMemoryError.

That's it for this blog. Hope you learned something new today.

Do share this blog with your fellow developers to spread the knowledge. You can read more blogs on Android on our blogging website.

Have a look at our Interview Kit for company preparation.

Apply Now: MindOrks Android Online Course and Learn Advanced Android

Happy Learning :)

Team MindOrks!

Also, Let’s connect on Twitter, Linkedin, Github, and Facebook