Understanding the RenderThread
RenderThread is a new addition to Android Lollipop. The documentation for this component is sparse, with only a vague definition:
RenderThread is a thread managed by the system. RenderThread animations play more smoothly than UI thread delays.
To understand how RenderThread works, you need to introduce some basic concepts.
When hardware acceleration is enabled, Android uses the “Display List” component to draw each frame instead of using the CPU directly to draw each frame. The Display List is a series of records of drawing operations abstracted into the RenderNode class.
This indirect drawing operation has many advantages:
- The Display List can be drawn as many times as needed without interacting with the business logic.
- Specific draw operations (such as translation, scale, etc.) can be applied to the entire display list without redistributing the draw operations.
- Once all the drawing operations are known, you can optimize for them: for example, all the text can be drawn together once.
- Processing of the display List can be transferred to another thread (not the UI thread).
The last point happens to be the RenderThread’s responsibility: performing optimizations outside of the UI thread and distributing drawings to the GPU.
Before Lollipop, it was almost impossible to animate a View property (for example, transition animation between activities) while performing a “redo” operation. On Lollipop and above on Android, these animations and other effects (such as Ripple) work smoothly. This dark technology comes with the help of RenderThread.
The real renderer is the GPU, and the GPU knows nothing about animation: the only way to do animation is to distribute a different draw action for each frame to the GPU, but the logic itself cannot be performed on the GPU. If the action is performed on the UI thread, any reaction will prevent new draw instructions from being distributed in time, and the animation will be delayed.
As mentioned earlier, RenderThread can handle some parts of the display List process, but note that the creation and modification of the Display List still needs to be done in the UI thread.
So how do animations get updated from different threads?
With hardware acceleration enabled, Canvas is implemented by the DisplayListCanvas class, which overloads some of the drawing methods, The method parameter type replaces the original primitive type with a CanvasProperty object (for example, the Float type is replaced with a CanvasProperty
), which is a wrapper class for the original type. In this way, the DislPlay List and its corresponding draw operations can be created in the UI thread, and the parameters of the draw method can be modified dynamically through the mapping of the CanvasProperty and asynchronously through the RenderThread.
There is one more step to do this: the value of the CanvasProperty needs to be animated by RenderNodeAnimator, which contains the configuration and initial value of the animation.
This kind of animation has some interesting features:
- The target
DisplayListCanvas
This parameter must be set manually and cannot be modified - No concern after the animation is sent: the animation can only be cancelled (no pause or resume) after it has been executed and there is no way to know the value of the property at the moment
- You can set up custom interpolators that will execute on RenderThread
- Start delay Usually waits on the RenderThread
To date, the following animations can be performed with RenderThread:
View property (via Viewanimate
Method execution)
- Translation (X, Y, Z)
- Scale (X, Y)
- Rotation (X, Y)
- Alpha
- Circular Reveal animation (pass
ViewAnimationUtils
的createCircularReveal
Execute)
The Canvas method and the Canvas property
- drawCircle(centerX, centerY, radius, paint)
- drawRoundRect(left, top, right, bottom, cornerRadiusX, cornerRadiusY, paint)
Paint properties
- Alpha
- Stroke width
It seems that Google has only encapsulated the drawing required for Material Design animation.
On Android N and above, the capabilities of RenderThread have been extended (for example, AnimatedVectorDrawable will use RenderThread to animate), and RenderThread may become an open API in the future.
In short, can I animate on RenderThread?
According to the documents, no. Unless you use the View. The animate or ViewAnimationUtils. CreateCircularReveal.
You can Hack it. For non-open components, we can use reflection to get references to related classes and methods, wrap classes for type safety, provide feedback when reflection fails, and so on.
I have implemented a library for executing animations using RenderThread.
Using the library is simple in three steps:
CanvasProperty<Float> centerXProperty;
CanvasProperty<Float> centerYProperty;
CanvasProperty<Float> radiusProperty;
CanvasProperty<Paint> paintProperty;
Animator radiusAnimator;
Animator alphaAnimator;
@Override
protected void onDraw(Canvas canvas) {
if(! animationInitialised) { // 1. create as many CanvasProperty as needed with the initial animation values centerXProperty = RenderThread.createCanvasProperty(canvas, initialCenterX); centerYProperty = RenderThread.createCanvasProperty(canvas, initialCenterY); radiusProperty = RenderThread.createCanvasProperty(canvas, initialRadius); paintProperty = RenderThread.createCanvasProperty(canvas, paint); // 2. create one or more Animator with the properties you want to animate radiusAnimator = RenderThread.createFloatAnimator(this, canvas, radiusProperty, targetRadius); alphaAnimator = RenderThread.createPaintAlphaAnimator(this, canvas, paintProperty, targetAlpha); radiusAnimator.start(); alphaAnimator.start(); } // 3. draw to the Canvas RenderThread.drawCircle(canvas, centerXProperty, centerYProperty, radiusProperty, paintProperty); }Copy the code