Draw What You See! … and clip the #e11 out of the rest.
Every time your app draws a pixel on the screen, it takes time. Every time your app draws an opaque pixel to replace something that has already been drawn, it wastes time. Drawing a pixel more than once per screen refresh is called overdraw, and it’s a common problem affecting the performance of modern applications. To be efficient, every time your app refreshes the screen, it should strive to draw every changed pixel only once.
For example, an app might draw a stack of 52 overlapping cards, with only the last card fully visible. Completely drawing the 51 cards that are underneath and partially covered is an example of overdraw.
The most likely symptom you will see in an app with overdraw is slow rendering and stuttering animations. This is the most generic of symptoms. So, since overdraw is common, and straightforward to test for, we recommend you make it a habit to check for it every time you change the views of your app.
Test for overdraw.
You can visualize overdraw using color tinting on your device with the Debug GPU Overdraw tool.
To turn on Debug GPU Overdraw on your mobile device:
- Open Settings.
- Tap Developer Options.
- In the Hardware accelerated rendering section, select Debug GPU Overdraw.
- In the Debug GPU overdraw popup, select Show overdraw areas.
- Watch your device turn into a rainbow of colors. The colors are hinting at the amount of overdraw on your screen for each pixel. True color has no overdraw. Purple is overdrawn once. Green is overdrawn twice. Pink is overdrawn three times. Red is overdrawn four or more times.
Obey the one rule.
If at any given point your app is drawing something that the user does not see, don’t draw it.
In particular:
- Eliminate unnecessary backgrounds.
- Clip generously and redraw only areas of the screen that have changed.
- Simplify and reorganize your view hierarchy to minimize overlapping in the first place. (This is a big topic, so it will be covered in the next article.)
Eliminate unnecessary backgrounds.
This fix is as straightforward as it gets. Search your code for “android:background”, and for each background, determine whether it’s needed and visible on the screen. If the view’s background is covered by something else, for example an image or the children’s backgrounds, remove the android:background line of code from the view.
Before:
<ImageView
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:src=”@drawable/beach”
android:background=”@android:color/white” />
After:
<ImageView
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:src=”@drawable/beach” />
Clip generously.
For standard views, the Android system will reduce overdraw as much as it can and avoid drawing views that are completely hidden. For example, everything underneath an open navigation drawer is not drawn.
clipRect()
For complex custom views where you’re overriding the onDraw method, the underlying system doesn’t have insight into how you’re drawing your content, which makes it hard for it to know what to avoid. You can help the system by using the Canvas.clipRect API. This function allows you to define a rectangle for a view, and only the stuff inside the rectangle will be drawn.
So, for that set of stacked overlapping cards, you can determine what part of the current card is visible, and set your clipping rectangle accordingly.
Clipping saves performance on both the CPU and the GPU.
On the CPU side, each canvas draw command has a bit of overhead once it is submitted to OpenGLES to draw. Any draw commands that lie entirely outside the stated clipping rectangle won’t be submitted to the hardware, and thus won’t produce that overhead. (See Android UI and the GPU or wait for an upcoming article to get into the details.),
Now, anything that partially intersects with the clipping rectangle will still be drawn, but on the GPU side, clipRect will define an exclusion rectangle that allows the GPU, on a per-pixel level, to avoid coloring anything that’s clipped.
quickReject()
Now, even if you only draw a small circle in a corner of your custom view, that entire view is rebuilt. Instead of recalculating and redrawing the whole screen, you can calculate the clipping rectangle for the changed area, then use the quickReject() API to test for clipping rectangle intersections inside your onDraw function. If some part of a view that takes up a lot of processing time is outside the clipping rectangle, quickReject can tip you off, so that you can skip that processing altogether.
Complex clipping
Beyond clipRect and quickReject, the Canvas class provides functions for complex clipping (clipPath, clipRegion, or applying clipRect while rotated). This kind of clipping can be expensive and, in addition, isn’t anti-aliased. The general advice is to always compose drawing elements of the right shape and size instead of clipping, as for example drawCircle or drawPath is much cheaper than clipPath with drawColor.
Of course! There are trade-offs.
Unlike eliminating backgrounds, clipping is not free, especially on older and lower-end devices. So, don’t clip when you don’t need to! However, much better than doing oodles of clipping, only use custom views when you have to, and arrange your user interface and view hierarchy to avoid partially overlapping views in the first place. (And we’ll talk about that in the next installment.)
Overdraw still matters.
Like with many other performance challenges, a little, or even a lot, of overdraw and extra redrawing may not matter on your snazzy new mobile phone. But most people in the world have less powerful devices. Reducing overdraw for them can save significant resources and greatly decrease the time it takes to draw a frame.
More generally, reducing overdraw may have a significant impact on app performance if it is the biggest performance issue your app has, but it may have no noticeable impact if you are on a fast device. Finally, it may have little measurable impact, if your app has other, much bigger performance issues, such as…but that would be telling.
For a more entertaining version of this section, watch Understanding Overdraw and ClipRect and ClipReject, which are part of Android Performance Patterns on YouTube, where you can also find more on clipping. Get your hands dirty with the Debug GPU Overdraw Codelab. If you want to jump ahead, check out the Android Performance Course on Udacity. But most importantly, join our Android Performance G+ Community for great tips about, you guessed it, building performant Android apps.