[MUSIC PLAYING]
Did you know that Android has over 70 standard views
built into the operating system?
But no matter how many standard views there are,
sooner or later, you're going to want something
that the built-in components just can't do.
Luckily, Android has great support for custom views.
It's easy to make a view that changes built-in behavior
or does something new altogether.
What can be hard is making that custom view perform well.
My name is Ian Ni-Lewis, and I'll
show you how to avoid some of the performance pitfalls
that come with making a custom view.
See, the standard views that come with the framework
have been around a long time.
And the Android team has had plenty
of opportunities to make these views better and faster.
When you use those built-in views,
you get the benefit of all those years of tuning for free,
but when you create a view that does its own custom drawing,
you're on your own.
Let me give you an example.
I noticed that some of my coworkers
are way too productive, so I'm going
to write them a game of solitaire
to keep them occupied.
And naturally, that playing field
is going to be a custom view.
So there's three main categories of mistake
I could make with this view, performance wise.
First, I could waste time drawing things
that haven't changed.
Second, I could waste time and bandwidth drawing pixels
that will never be visible because they're covered up
by something else.
And third, I could spend too long running code in onDraw.
So let's start with drawing things that haven't changed.
Remember, all drawing starts with a call to View.invalidate.
The first rule of custom views is to never call invalidate
when you don't have to.
If nothing in the view has changed,
then doesn't need to be redrawn.
And the second rule of custom views
is to always pass a rectangle to invalidate.
Now, that rectangle is called the dirty Rect.
It's a hint to the system to tell it which part of the view
has changed.
So the system uses that rectangle
to help it decide when to call onDraw.
For instance, if your dirty Rect is behind another view,
then there's no point in redrawing anything.
So being smart about when and how we
call invalidate means that onDraw gets
called less frequently.
But sooner or later, it does get called.
So that's when things get interesting.
See, the performance of onDraw is
what determines whether an app is a fluid, magical experience,
or an irretrievable jank fest.
And the easiest way to destroy your onDraw performance,
at least on some devices, is overdraw.
Let me show you what I mean.
You see, check out these overlapping cards.
You can't see it, but every card in that view
is being drawn completely, even the parts that are going
to be hidden by the card above.
And because you can't see it in the final product,
it's wasted work.
Now, in another video, we talked about the debug GPU overdraw
feature that's built in into the Android developer options.
If you turn that on, almost all of these cards
are going to turn red.
That's thousands of wasted pixels,
and every single one of them costs in two ways.
First, when the bitmap is read from memory.
And second, when the frame buffer is written.
So how can we get rid of all that waste?
Well in this example, I know I've got overlapping views,
so I want to tell the GPU to only draw
the visible part of each element.
I can do that by setting the clip rectangle.
The clip rectangle is the area of the screen that
is affected by a draw call.
The pixels outside of the rectangle don't get touched.
They don't cost anything.
The method that lets me do that is Canvas.clipRect.
When you call this method, the output of every subsequent draw
call is limited on the GPU to that rectangle,
and that lasts until the next call to clipRect.
Now, the performance gain we see from clipRect
is going to vary some, based on the GPU you're targeting.
On lower end GPU, gain is dramatic.
But if you're targeting higher end GPUs,
you might not see much benefit at all from clipping.
You know, I tested my solitaire code
on a Nexus 9, which has got a relatively powerful GPU.
And I found that I was actually spending more time
on the CPU calculating the clipRects than I
saved on the GPU by eliminating overdraw.
Which brings us to the most difficult and most important
part of custom views, reducing the CPU cost on onDraw.
Here's a few tips to help you with that.
One, don't draw anything you don't have to.
A clipRect is going to prevent the GPU from drawing offscreen
pixels, but it can't reduce the CPU cost of a draw call.
And some drawing calls do need to use a lot of CPU.
That's especially true when you're laying out text.
So call Canvas.quickReject to identify elements that
aren't currently on screen.
If quickReject returns true, don't draw that element.
And quickReject won't return true for drawing elements that
are partially unseen, only for elements
that are completely invisible.
And then hint number two, don't use draw methods that
aren't hardware accelerated.
Check out this handy guide.
It shows you everything that's hardware accelerated
and everything that isn't on each version of Android.
Now, hardware accelerated means that the drawing
happens on the GPU.
Now, if a method isn't hardware accelerated,
that means it needs to use the CPU to draw.
And then it has to send the results to the GPU
to get composited with the rest of the view.
Compared to accelerated drawing, this
is horribly, horribly slow.
So if the method you want isn't accelerated,
you might want to see if you could find a different way
to draw your view.
Finally, don't make allocations in onDraw.
Don't make allocations in any code
that gets called from onDraw.
Allocations cost two ways.
First, there's the immediate cost
of allocating and initializing an object.
And then later, there's the cost of garbage collection.
Either one of those costs can be enough to cause
a dropped frame.
Now, a lot of this advice might seem
like nit-picking over relatively small perf gains.
But keep in mind when your view is animating,
onDraw gets called 60 times a second.
And unlike most other code, it has to run on the UI thread.
So bad performance in onDraw is going to translate directly
into jank in your app.
You've got 16 milliseconds, at most, to draw the screen.
Realistically, once you factor in overhead
from other parts of your app and potential GCE pauses,
you've probably got half that.
Every millisecond counts.
And if you like counting milliseconds as much as we do,
you'll definitely want to check out all the other great info
on the Android Performance Patterns homepage.
Don't forget to join our Google+ community.
And remember, keep calm, profile your code,
and as always, perf matters.
[MUSIC PLAYING]