Sunday, 23 August 2015

Android App Profiling and Optimization

Android is vast and there're often many solutions to a certain problem. On Stackoverflow, solutions that get many votes tend to be our safest bet, especially when many users give positive feedback in the comments. The situation becomes trickier when there's a background operation involved, running asynchronously. I often stumble upon solutions that completely ignore the impact they have on the performance and stability of the app. In some cases this might cause slight performance decrease, but in other cases, it might cause the app to crash!


 The Splash Screen Example


Please note: the following is just an example of unoptimized code and is not a solution I would recommend for implementing a splash screen. For a proper solution please see Ian Lake's Pro-tip.

Let's say we want to create a splash screen. We have an idea how to do it, but we're unsure if that's the best way. Since we're keen on best practice, we google for it. In this case, we get different solutions, some from SO and some from blogs. For instance, this solution:


public class Splash extends Activity {
    /** Duration of wait **/
    private final int SPLASH_DISPLAY_LENGTH = 1000;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splashscreen);

        /* New Handler to start the main Activity 
         * and close this Splash-Screen after a few seconds.*/
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                /* Create an Intent that will start the main Activity. */
                Intent mainIntent = new Intent(Splash.this, MainActivity.class);
                Splash.this.startActivity(mainIntent);
                Splash.this.finish();
            }
        }, SPLASH_DISPLAY_LENGTH);
    }
}



This is a very common solution found on the web. It has a dedicated Activity for the splash screen that instantiates a Handler and calls postDelayed with a Runnable. After the delay time passed it will execute the Runnable, starting the main Activity followed by closing the splash screen Activity.

You copy-paste the code and yey it works and you're happy. So far so good.


 Analyzing the Code


Looking at the code I see a couple of flaws:

1. An infinite splash screen when the user changes orientations continuously while the splash screen is shown.
2. Memory leak.

As this blog post is about profiling and optimization, I'll concentrate on issue #2.
But wait a second, why should you worry about memory leaks? It's Java you are programming here and unlike native languages, such as C++, Java is managed. So why should you care? After all, it's done automatically for you, right? WRONG!


 Profiling and Optimizing the Code


This issue is often known in the Android community as "Leaking an Activity". Now what exactly does that mean?

When configuration change occurs, such as orientation change, Android destroys the Activity and recreates it. Normally, the Garbage Collector will just clear the allocated memory of the old Activity instance and we're all good.

"Leaking an Activity" refers to the situation where the Garbage Collector cannot clear the allocated memory of the old Activity instance since it's being (strong) referenced from an object that out lived the Activity instance. Every Android app has a specific amount of memory allocated for it. When Garbage Collector cannot free up unused memory, the app's performance will decrease gradually and eventually crash with OutOfMemory error.

How to determine whether the app leaks memory or not? The fastest way is to open the Memory tab in Android Studio and pay attention to allocated memory as you change the orientation.
If the allocated memory keeps on increasing and never decreases then you have a memory leak.

Example of memory leak in action
So how do we solve it in this case?


public class Splash extends Activity {
 // 1. Create a static nested class that extends Runnable to start the main Activity
    private static class StartMainActivityRunnable implements Runnable {
        // 2. Make sure we keep the source Activity as a WeakReference (more on that later)
        private WeakReference mActivity;

        private StartMainActivityRunnable(Activity activity) {
         mActivity = new WeakReference(activity);
        }

        @Override
        public void run() {
         // 3. Check that the reference is valid and execute the code
            if (mActivity.get() != null) {
             Activity activity = mActivity.get();
             Intent mainIntent = new Intent(activity, MainActivity.class);
             activity.startActivity(mainIntent);
             activity.finish();
            }
        }
    }

    /** Duration of wait **/
    private final int SPLASH_DISPLAY_LENGTH = 1000;

    // 4. Declare the Handler as a member variable
    private Handler mHandler = new Handler();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splashscreen);

        // 5. Pass a new instance of StartMainActivityRunnable with reference to 'this'.
        mHandler.postDelayed(new StartMainActivityRunnable(this), SPLASH_DISPLAY_LENGTH);
    }

    // 6. Override onDestroy()
    @Override
    public void onDestroy() {
     // 7. Remove any delayed Runnable(s) and prevent them from executing.
     mHandler.removeCallbacksAndMessages(null);

     // 8. Eagerly clear mHandler allocated memory
     mHandler = null;
    }
}



  1. We first create a static nested class called StartMainActivityRunnable. It is important that the class is declared 'static', as non-static nested classes have implicit reference to their parent class - yet another reason for memory leaks. You may also choose to create this class independent of the Activity class; either way is good. 
  2. Then in StartMainActivityRunnable, we declare a WeakReference to Activity as a member variable and instantiate it via the constructor. We use weak reference since StartMainActivityRunnable might run after the Activity has been destroyed (for instance, in case the user has changed the orientation), and therefore in that case we don't want to prevent the Garbage Collector from clearing its allocated memory. 
  3. Finally, when it comes to StartMainActivityRunnable implementation, we first check that the reference to Activity is still valid before we execute the code to start the main Activity.
  4. Next, dealing with the client code that uses StartMainActivityRunnable, we declare Handler as a member variable of Activity class. This is very important, because we want to control it later on in the Activity lifecycle.
  5. We then call postDelayed on mHandler, passing a new instance of StartMainActivityRunnable with a reference to the current activity instance.
  6. Last but not least, and this is the most important part here, we override onDestroy()
  7. and call removeCallbacksAndMessages(null) on mHandler. This method will clear any pending Runnable(s) from executing after the Activity is destroyed!
  8. And finally, we eagerly set mHandler to null so it can be garbage collected a.s.a.p.

Checking the memory tab again, we can see that the graph doesn't change and remains flat.

Great job! We've fixed a significant memory leak and improved the app's performance.
Make sure to read my the next blog post, where I describe how your app can notify you of memory leaks and provide you a more efficient way to track them down using a library called LeakCanary by Square.