When someone asks me about poor animation performance, one of the first questions I ask is whether or not they are using hardware layers.
During animations your views may be redrawn each frame. If you use view layers, instead of having to redraw each frame, views render once into an off-screen buffer which can be reused.
In addition, hardware layers are cached on the GPU, which makes certain operations during animation much faster. Simple transformations (translation, rotation, scaling and alpha) can be rendered quickly with layers. Since many animations are just a combination of these transformations, layers can supercharge animation performance.
Usage
The layer API is simple: just use View.setLayerType()
. You should only temporarily set hardware layers because they aren't free (more on that later). The basic process goes thus:
- Call
View.setLayerType(View.LAYER_TYPE_HARDWARE, null)
on eachView
you want to cache during animation. - Run your animation.
- On animation end, cleanup with
View.setLayerType(View.LAYER_TYPE_NONE, null)
.
Here's the above in action:
代码语言:javascript复制// Set the layer type to hardware
myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// Setup the animation
ObjectAnimator animator = ObjectAnimator.ofFloat(myView, View.TRANSLATION_X, 150);
// Add a listener that does cleanup
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
myView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
// Start the animation
animator.start();
If you're on minSdkVersion 16 and you're using ViewPropertyAnimator
then you can replace the above with the handy withLayer()
method:
myView.animate()
.translationX(150)
.withLayer()
.start();
And just like that, your animations are smooth!
Caveats
...You know it couldn't be that easy.
Hardware layers are almost magical in their ability to increase animation performance. However, if misused, they can do more harm than good. Do not blindly apply layers!
First, in some cases, hardware layers may actually be doing more work than simple view rendering. Caching a layer takes time because the first pass is a two-step process: first the views are rendering into a layer on the GPU, then the GPU renders that layer into the window. If the view rendering is simple (such as a flat color) then a hardware layer may add unnecessary overhead during initialization.
Second, as with all caching, there is a possibility of cache invalidation. If anyone calls View.invalidate()
during your animation then the layer will have to re-render again. Constantly invalidating your hardware layers can actually be worse than no layers at all since (as stated above) hardware layers have added overhead when setting up the cache. If you're constantly having to re-cache the layer, that can majorly hurt!
This problem is easy to run into because animations often have multiple moving parts. Suppose you're setting up an animation with three moving parts:
代码语言:javascript复制Parent ViewGroup
--> Child View 1 (translates left)
--> Child View 2 (translates right)
--> Child View 3 (translates up)
If you set a single layer on the parent ViewGroup
you'd actually have nonstop cache invalidation because (as a whole) the ViewGroup
is constantly changing due to its children. Each of the individual Views
, however, are just translating. In this case, it'd be best to set a hardware layer on each of the child Views
(and none on the parent).
To reiterate, since I didn't understand this at first: it is often appropriate to set hardware layers on multiple views such that none of them invalidate during the animation.
"Show hardware layers updates" is a great developer tool for tracking down this issue. It flashes views green whenever a view renders a hardware layer. It should only flash once when the animation starts (i.e. the initial layer render). However, if your view is solid green the entire animation, then you've got invalidation problems.
Third, hardware layers take up GPU memory and obviously you don't want any memory leaks. Therefore, you should only use hardware layers when necessary, like during animations.
All that said - there's no hard rules here. The Android rendering system is complex and often surprises me. As with all performance problems, measurement is key. The "profile GPU rendering" and "show hardware layers updates" developer settings are great for determining if layers are helping or hurting your cause.
Sample
I wrote a sample app that demonstrates basic hardware layer usage. You can get the source code here.
Here it is running on my Galaxy Nexus (an old, slow device) with profile GPU rendering enabled:
Without hardware layers this simple animation is terrible. It's constantly above the green line which means it looks like a janky mess. In contrast, the version with hardware layers stays below the green line the entire time - great!
The third case shows the danger of invalidation during an animation with hardware layers. A lot of the performance gains are killed due to bad usage of hardware layers.
(There is some oddity here - if it's invalidating, why isn't it as slow as not having hardware layers at all? I don't fully understand it myself, but apparently there are some optimizations hardware layers make possible that are advantageous even if they must redraw at each step. Still - it's better to use them correctly.)
The moral of the story: hardware layers can be great for animation but use them wisely!