Lottie Android 6.1 -Lottie goes multithreaded
In addition to a slew of bug fixes, new features, and under the hood improvements, the goal for Lottie 6.1 was to do a deep dive into performance. The vast majority of apps never experience performance issues with Lottie. However, Lottie is a library that runs within multiple apps on nearly every Android device on this planet so even incremental improvements matters.
I’d like to thank Airbnb for sponsoring this project. Inspired by the work that Cal Stephens did on iOS, I pitched the idea of doing a similar investigation on Android. Airbnb agreed to financially sponsor the time I spent on this particular project and it’s unlikely that it would have been done without their support.
Now, for some background on how Lottie renders animations. Every animation frame involves two phases: update
and draw
.
The update
phase is called from each animation tick. It walks the entire animation tree and asks every animatable node (color, position, opacity, shape, etc) if it changed. If it did, it will calculate what its new value is, cache internal values like computed colors and paths, and return.
The draw
phase does exactly what you would expect. It take the computed values from update
and draws them to a Canvas
.
It would be reasonable to assume that drawing is the most expensive part of a Lottie animation. However, in many cases, update
is actually slower. Unless your animation has large mattes or masks, thanks to hardware acceleration and incredible improvements to mobile GPUs and multicore CPUs over the years, drawing is extremely fast. Just look at what mobile games are capable of in 2023.
Prior to Lottie 6.1 with the new asyncUpdates
setting enabled, Lottie would call update
and draw
synchronously on the main thread on every frame. The draw phase must be on the main thread. However, nothing about update
is thread specific. The only constraint is that the two should never overlap or else you could wind up drawing half of one frame and half of another at the same time.
The new asyncUpdates
flag creates a dedicated high priority background thread to handle the update
phase. During the draw phase, Lottie will check if it has called update
within two frames of the current animator value. If it has, it will draw whatever the latest values are immediately. If there is any difference between what was drawn and what the latest animator value is, it will enqueue update
to begin on a background thread immediately after draw
finishes. Both phases lock a mutex to ensure that they never happen concurrently.
To demonstrate this visually, this is what Lottie looks like without asyncUpdates
And here is what it looks like with asyncUpdates
As you can see, not only is the majority of the time spent in the update phase, but with asyncUpdates
, that entire block moved to a background thread. In this case, the update
phase for the next frame was already complete by the time the non-Lottie parts of the screen finished drawing.
Because this is the first time Lottie has gone multithreaded, asyncUpdates
will be released as an experiment API which means that it may change and evolve over time to ensure that it is stable and reliable for all use cases. Please check out the full changelog, file issues or suggest further improvements if you find any during your own testing.
As always, you can reach me on Twitter, Threads, or GitHub. This project was made possible with the direct support of Airbnb and continued sponsorship of Lottiefiles, Lottielab, American Express, Emerge Tools, Robinhood, Stream, and several smaller sponsors.
If your team uses Lottie, you can help make future projects like this possible and join our private community Slack by sponsoring the project on GitHub or OpenCollective.