Memory Leaks in Android: Find, Fix, and Avoid
Memory Leaks in Android: Find, Fix, and Avoid

Memory Leaks in Android: Find, Fix, and Avoid

Categories

  • Articles

Services

Nobody likes it when apps glitch and freeze. Especially when it happens at the most inconvenient moment. When we book a taxi, bet on a game at the last second, or turn into the wrong lane while using maps navigation on our phones. All this makes people frustrated and creates a bad experience with the product, brand, apps, and as a result, the download statistics go down. 
Every developer knows, “When download statistics go down, it’s time to analyze why crash and ANR statistics are up”.
One of the reasons for glitches, crashes and freezes can be memory leaks. Now we will talk about how to identify them in Android applications and how to deal with them. 

reasons of memory leaks in Android

What are memory leaks? 

Most Android native apps are written on Java or Kotlin. That means that we have a Virtual Machine with a Garbage Collector (GC). 

“Garbage Collection deals with finding and deleting the garbage from memory. However, in reality, Garbage Collection tracks each and every object available in the JVM heap space and removes unused ones.” 

A memory leak happens when memory is allocated but never freed. This means the GC is not able to take out the trash once we are done with the takeout.  
Android has a 16ms drawing window, and the GC normally takes less time to deal with memory. When the heap of allocated memory is increasing in unevenly large portions and memory is not being deallocated, the system will force a larger GC to kick off, which pauses the entire application’s main thread for around 50ms to 100ms. 

Does my application have memory leaks? 

First of all, we need to find and detect memory leaks in the application. Let’s look over some of the ways and tools you can use to do that. 

mempry leaks in Adnroid tools to indentify

  • Android Studio Memory Monitor 
    Android Studio provides handy tools for profiling the performance of your app. One such tool is the Memory Monitor. Open the bottom tab in Android Studio while the app is running on a device or emulator. You can see how much memory your app has allocated at the moment. Memory heap will be increasing and decreasing depending on the actions that the application does at the moment. If you notice that memory is allocated but doesn’t get deallocated in a short period of time, this is probably what we are looking for. 
  • Infer 
    Infer is a static analyzer tool made by Facebook. This CI tool helps you find possible null pointer exceptions and resource leaks, as well as annotation reachability, missing lock guards, and concurrency race conditions. You can read more on the getting started page.  
  • ANR statistics on Google Play Console 
    Some of ANR (“Application Not Responding”) may be caused by big heap allocating and UII freezes for more than 50 – 100ms. You can check them on Google Play Consoles or read more here
  • Crashes with OutOfMemmoryException 
    This is a 100% proven indicator that you have a memory leak. You should check all the resources and Context-related variables used in classes that you see on the crash stack. 
  • LeakCanary 
    With its vast knowledge of the Android Framework internals, LeakCanary has a unique ability to narrow down the cause of each leak. This helps developers dramatically reduce OutOfMemoryError crashes. You can read more on the getting started page.  

How to fix Memory Leaks? 

  • Contexts 
    Android SDK is full of classes that have Context as a parent class. Such classes and abstractions can be called “context-related”. Official documentation defines Context as such: 

“Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.” 

What we need to remember about memory leaks + context is that everything relating to Context, such as Activities, Services, Application, has a certain scope and a limited lifespan. So we can’t hold a reference to these types of classes because the state is rapidly changing.  
GC can’t clean resources when the reference of Activity or Context is present, but Activity is Destroyed and does not exist for us. 

You should not:  

  1. Avoid using static variables for views or context-related references. 
  2. Pass a context-related reference to a Singleton class. 
  3. Improperly use Context. Use applicationContext() instead of activity context or view context when it’s possible. For example, for Toasts, Snackbars. 
  4. Use a weak reference of the context-related references when needed. 
  • Static and View References 

Just forget about static when you are working with context-related classes. You don’t need it in Activity for Views, and you don’t need to hold Context everywhere.

If you still can’t manage your logic without such a principle, your architecture or pattern can be wrong for that scenario. Use official guidelines and look into Dependency Injection or app\data\domain modules. 

  /*
     * -> Static and View References 
     * Bad practice: Static context-related variables
     */
    companion object {
        private lateinit var textView: TextView
        private lateinit var activity: Activity
    }
  • Unregistered listeners 

Listeners and receivers, such as BroadcastReceiver, LocationListener, Bluetooth and BLE listeners, have a strong reference to Fragment or Activity, and they hold them until you unregister them.  

Carefully check your onCreate (onCreateView), onStart, and onResume methods. These lifetime methods typically have registered events. For each registered event, you should have unregistered opposing methods onDestroy, onStop, onPause.  

/*
        * -> Unregistered listeners
        * Note: Take care about jobs and architecture to avoid leaks. Use Android Work manager
        * or use RxJava or any safe jobs scheduling library.
        */

        private lateinit var leakyCountDownTimer: CountDownTimer
        private lateinit var leakyRunnable : Runnable
        private lateinit var leakyThread : Thread

        override fun onCreate(@Nullable savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            startTimer()
            startRunnable()
            startThread()
        }

        override fun onDestroy() {
            super.onDestroy()
            /*
            * Bad practice: Don't stop runnables, threads and other timers on onDestory/onStop/onPause
            */
        }

        fun startTimer() {}
        fun startRunnable() {}
        fun startThread() {}
    }
}
  • Inner classes and anonymous classes

Here we need to notice that the outer class should never have a static reference to the inner class. The inner class should never have a hard reference to the outer class, especially when talking about context-related classes.

Anonymous classes, such as Handler().postDelayed() or classes that emit a new thread (Runnable, AsyncTask), should stop and close this thread before being destroyed. Take care of every thread and resource that you have created.

/*
     * -> Inner classes and anonymous classes
     * Note: Take care about navigation and architecture to avoid bad practices with context-related variables
     */

    class SomeActivity : AppCompatActivity() {

        override fun onCreate(@Nullable savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            leakyClass = InnerClass(this)
            leakyClass.goSomeActivity()

             //Some jobs here..

            leakyClass.goSomeActivity()
        }


        /*
        * Bad practice: Context-related logic inside inner class that may produce creating/destroying
        */
        private inner class InnerClass(private val activity: Activity) {

            fun goSomeActivity() {
                activity.startActivity(Intent(activity, SomeActivity::class.java))
            }
        }

        /*
        * Bad practice: Static variables of inner class
        */

        companion object {
            private lateinit var leakyClass: InnerClass
        }
    }
  • Singletons 

The memory leak could happen when we initialize the singleton from the activity and pass a long-lived context-related reference to the singleton constructor when we initialized it. Singleton class will hold a reference to Context after destroying the initial resource.

Try to hold applicationContext() instead, or use Context as a parameter of Singelton methods.

 /*
     * -> Singletons  
     * Bad practice: Not application context variable inside Singleton
     * Bad practice: Not destroying context inside onDestroy
     */
    inner class SingletonClass(var context: Context) {
        lateinit var instance: SingletonClass
    }
  • Bitmaps

Inefficient use of Bitmaps without recycling it. Managing Bitmaps may easily throw OutOfMemoryException.

Find out more about memory management for Bitmaps in the official documentation here.

Conclusion

Most of the time we spend on user stories, development, and distribution. That’s why we focus on building features, functionalities, and the UI components of our apps. 
We forget to focus on the core issue like performance and quality of the app, which is a major part of an application. 
Try to indicate memory leaks in your application and listen to this simple advice on writing code without unnecessary dependencies and excessive use of resources. Thus, you will certainly be able to protect the application from memory leaks. 
So to rehash, if you want to avoid memory leak scenarios, you should incorporate in your work these best practices: 

  • Take care of context. 
  • Use common architectures for Android projects, such as Clean, ViewModel, or UseCase Architectures. A clean and structured project is the best way for avoiding most issues. Here is the official architecture guide from Google. 
  • Delegate referencing to DI and inject context-related references. 

Find out more about Android Performance Patterns on the official Google Developer channel on YouTube. 

Arsen Boikanych

Arsen Boikanych

Senior Android Engineer at Symphony Solutions

Arsen has been a Senior Android Engineer at Symphony Solutions for more than 2 years. He first got into mobile development back at the university. He worked in mobile teams on different projects before he found a new obsession with startups and decided to dive into a new endeavor. First, it was Senstone, a hardcore Ukrainian startup that makes gadgets for sound recording with a speech-to-text function. Then came a large stack of Bluetooth BLE projects, ranging from apps for identifying faulty car parts to creating neural networks for text classification. He’s also working on his own project chichi.com.ua where he gets to practice his tech and managerial skills all at once. 

Share
You might be interested
How to prepare for and pass ISTQB certification: My experience

How to prepare for and pass ISTQB certification: My experience

May 17, 2021 · 5 min read
How to Become a Full-Stack Developer? 

How to Become a Full-Stack Developer? 

Mar 9, 2021 · 5 min read
Announcement Banner