Jon Douglas The rantings of a developer

Xamarin.Android - UI Performance

Android UI Performance

NOTE: This blog was featured on blog.xamarin.com in which you can view a condensed version here: https://blog.xamarin.com/tips-for-creating-a-smooth-and-fluid-android-ui/

As developers, we want our users to have buttery smooth experiences when using our application. When it comes to UI performance, a buttery smooth experience can be defined as a consistent 60 frames per second(fps). That means that we have to render a frame every 16 ms to achieve this experience.

Note:

You can exceed the 16 ms time to render a frame occasionally, as there are often one or two buffered frames ready to go.

Why 60 fps?

Watch this wonderful video by Google on this topic for a brief introduction:

Why 60 FPS

This gives our whole frame process a little less than 16 ms to fully draw a frame on the screen. If a frame is dropped or delayed, it is known as Jank.

Identifying the problem

Have you ever had an experience like this with your application? Your scrolls are very choppy and your UI doesn’t seem to be that responsive.

Credit: Doug Sillars https://github.com/dougsillars for a great example.

During the time that the application is unresponsive and choppy, if the user attempts to interact with the application and it takes longer than 5000 ms (5 seconds) for the application to respond, the Android OS will give us a lovely message:

This isn’t the best experience we can offer our users.

Digging Deeper

Have you ever seen the following in your adb logcat?

I/Choreographer(1200): Skipped 60 frames!  The application may be doing too much work on its main thread.

This is the Android Choreographer warning you in advance that your application is not performing to the buttery smooth experience we are aiming for. In fact it’s telling you that you are skipping frames and not providing your users a 60 FPS experience. We will need a few tips & tricks to resolve these issues.

Overall Tips & Tricks for UI Performance

  • Measure overall UI performance with Systrace to get a baseline.
  • Use Hierarchy Viewer to identify and flatten view hierarchies.
  • Reduce overdraw by flattening layouts and drawing less pixels on screen.
  • Enable StrictMode to identify and make fewer potentially blocking calls on the main UI thread.

Getting Started

Counting Janky Frames

First you’ll want to get an overview of the total janky frames. After your application has been running and interacted with to reproduce the jank, run the following command:

adb shell dumpsys gfxinfo <PACKAGE_NAME>

Sample Output:

Stats since: 524615985046231ns
Total frames rendered: 8325
Janky frames: 729 (8.76%)
90th percentile: 13ms
95th percentile: 20ms
99th percentile: 73ms
Number Missed Vsync: 294
Number High input latency: 47
Number Slow UI thread: 502
Number Slow bitmap uploads: 44
Number Slow issue draw commands: 135

Numbers never lie, so it’s apparent we have a bit of Janky frames(~9%). Let’s dig in further with Systrace.

Systrace

Systrace gives you an overview of the whole android system and tells you what’s going on at specific intervals of time.

Getting Started

Let’s start a systrace on our device. First open up Android Device Monitor to get started.

Once inside, we see the option to start a Systrace:

We then are prompted with what we would like to trace:

This will generate a trace.html file that will give us information about our system for the trace duration.

EX: Systrace over 30 seconds:

Okay great! But what does this all mean? Let’s take it a step at a time.

Alerts & Frames

Alerts will give you a description of what the current situation is with a respective frame(s). It might let you know that there was a long View.OnDraw() call and it might give you suggestions on how you can fix the relevant frame(s).

You can then dig straight into the frame.

And see how much time spent during each step

Finally you can mark that frame using the m hotkey and see what work is being done on various threads such as various CPU Threads, the UI Thread, and the RenderThread.

This example is showing a Yellow Frame, but we can get a general idea of what an idea performant Frame might look like.

From our Alert, we can see that we might want to avoid significant work in View.OnDraw() or Drawable.Draw(), especially allocations or drawing to Bitmaps. In other words, Google gives us a tip to watch this video:

Avoiding Allocations in onDraw()

For our Frame, we can see that our Adapter.GetView() should recycle the incoming View instead of creating a new one.

Systrace Terms

Frame Color

Green - Great performance

Yellow - Less than ideal performance

Red - Bad performance

Scheduling Delay

Scheduling delays happen when the thread that is processing a specific slice was not scheduled on the CPU for a long amount of time. Thus it takes longer for this thread to fire up and complete.

Wall Duration

The amount of time that passed from the moment a slice is started until it’s finished.

CPU Duration

The amount of time the CPU spent processing that slice.

Overdraw

Debugging overdraw is fairly easy. You can enable this in your Android device’s settings:

Settings -> Developer Options -> Debug GPU overdraw -> Show overdraw areas.

Once enabled, you will see many different colors on your layouts.

  • White - No overdraw
  • Blue - Pixels that are 1x overdrawn
  • Green - Pixels that are 2x overdrawn
  • Pink - Pixels that are 3x overdrawn
  • Red - Pixels that are 4x overdrawn

Bad Layout Performance:

Good Layout Performance:

You can then identify why this layout might be overdrawing so much via a tool like Hierarchy Viewer.

Hierarchy Viewer

The first thing we want to know regarding our View Hierarchy is how deep or nested our layouts are.

Let’s take the previous example of bad layout performance:

We can see that we are 4 layers deep which is less than ideal for our ListView. We really need to flatten this out.

Okay that’s a little better! We are only 3 layers deep now. However it’s still not great. Let’s try to remove one more layer.

Much better! We just optimized our whole view hierarchy and we will reap the performance benefits. Let’s take a look at the overall Layout and Draw timings for proof.

  • Bad Layout - 31 views / Layout: 0.754 ms / Draw: 7.273 ms
  • Better Layout - 26 views / Layout: 0.474 ms / Draw: 6.191 ms
  • Good Layout - 17 views / Layout: 0.474 ms / Draw: 1.888 ms

StrictMode

StrictMode is a very useful tool for battle testing your application. Enabling StrictMode as a means to ensure you are not putting extra work in certain places of your application like disk reads, disk writes, and network calls is ideal for a great user experience. In a nutshell StrictMode does the following:

  1. Logs a message to LogCat under the StrictMode tag
  2. Display a dialog (If PenaltyLog() is enabled)
  3. Crash your application (If PenaltyDeath() is enabled)

This can help you determine what type of policies you’d like to battle test your application with.

Enabling StrictMode in your Android Application

protected override void OnCreate(Bundle bundle)
{
    StrictMode.SetThreadPolicy(new StrictMode.ThreadPolicy.Builder().DetectAll().PenaltyLog().Build());

    StrictMode.SetVmPolicy(new StrictMode.VmPolicy.Builder().DetectLeakedSqlLiteObjects().DetectLeakedClosableObjects().PenaltyLog().PenaltyDeath().Build());

    base.OnCreate(bundle);
}

Summary

There are a plethora of tools available to use on your Xamarin.Android application. Use them to track the important performance-related items about your application such as rendering performance to achieve a buttery smooth 60 fps experience for your customers. Use tools like Systrace, GPU overdraw, Hierarchy Viewer, and StrictMode to pinpoint performance related issues in your application and fix them.