Understanding the RenderThread
“What Google doesn’t want you to know” (not really)
RenderThread is a new component that was introduced in Android Lollipop. The documentation about it is so scarce that at the time of writing this post there are only 3 references to it, and just one very vague definition:
A new system-managed processing thread called RenderThread keeps animations smooth even when there are delays in the main UI thread.
In order to understand what it really does, there are a few concepts that need to be introduced…
When hardware acceleration is enabled, instead of executing the drawing calls directly on the CPU on every frame, Android uses a (hidden) component called “display list”: a recording of a set of drawing operations, represented by the class RenderNode (formerly DisplayList).
The benefits of going through such indirection are multiple:
- The display list can be drawn as many times as needed without further interaction with the business logic.
- Certain operations (like translation, scale, etc.) can be done on the entire list without the need to re-issue any drawing operation.
- Once all the drawing operations are known, they can be optimised: for example, all text is drawn together in one pass when possible.
- The handling of the display list can potentially be offloaded to another thread.
The last point is precisely one of the things that the RenderThread is now in charge of doing: handling the optimisation and dispatching to the GPU, away from the UI thread.
Before Lollipop you might have noticed that animating view properties smoothly while doing “heavy” work, like transitioning between different Activity, was essentially impossible. And yet, from Lollipop onwards, those animations and other effects (like ripples) keep animating as if nothing was happening. The trick is a little help from the RenderThread.
The real “executor” of the rendering is the GPU, which by itself doesn’t know anything about animations: the only way to animate something is to issue different drawing commands at each frame, and that logic cannot run on the GPU itself. When this is done on the UI thread, any heavy work will prevent the new drawing commands from being issued in time, hence the lag in updating whatever is being animated.
It was mentioned before that the RenderThread can manage certain aspects of the display list pipeline, but it’s important to note that the creation and modification of the display lists still has to happen on the UI thread.
Then how can animations be updated from a different thread?
When drawing with hardware acceleration, the Canvas implementation is a class called DisplayListCanvas (formerly GLES20Canvas) which has overloads of some drawing methods that, instead of accepting a direct value, expect a reference to a CanvasProperty, which in turn is a wrapper for such value. This way the display list can still be created on the UI thread with a static set of drawing calls, but the parameters for such calls can be changed dynamically (and asynchronously on the RenderThread) through the CanvasProperty mapping.
There’s one more step: the value of a CanvasProperty needs to be animated through a RenderNodeAnimator, which is how the animation is configured and started.
Some interesting properties of the resulting animation:
- A target DisplayListCanvas must be manually set, and can’t be changed afterwards.
- It’s “fire and forget”: after being started it can only be cancelled (no pause/resume) and there’s no way to know the current value.
- A custom Interpolator can be provided and the code will be called from the RenderThread.
- The start delay is generally waited on the RenderThread.
Here is what can be animated through the RenderThread (so far):
View properties (accessible through View.animate):
- Translation (X, Y, Z)
- Scale (X, Y)
- Rotation (X, Y)
- Circular reveal animation (accessible through ViewAnimationUtils.createCircularReveal)
Canvas methods (with CanvasProperty):
- drawCircle(centerX, centerY, radius, paint)
- drawRoundRect(left, top, right, bottom, cornerRadiusX, cornerRadiusY, paint)
- Stroke width
It looks like Google wrapped only the drawing operations that it absolutely needed to provide the Material design animations we have today. It may seem very limited, but with a bit of creativity it could still be used to implement different animations, from variations of ripples to entirely new effects. The advantage of doing it this way would be guaranteed jank-free animations that are offloaded from the UI thread.
It seems like the capabilities of the RenderThread are going to be expanded in Android N (for example AnimatedVectorDrawable is going to be animated there) and maybe one day it will be released as a public API.
TL;DR can I make my animations run on the RenderThread?
Unofficial, long answer: every component that was mentioned is hidden, so in order to use them one would have to get a reference to all classes/methods needed through reflection, wrap them nicely in order to maintain type safety, provide fallbacks when not available, and more…
Well, with this article I’m releasing a small proof of concept library that does just that!
It probably shouldn’t be used in production, but hey, I’m not your supervisor.
Using it is simple, there are generally 3 steps involved:
Be sure to check the sample for a complete implementation.