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 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.
draw phase does exactly what you would expect. It take the computed values from
update and draws them to a
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
drawsynchronously 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.
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
And here is what it looks like with
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.