Lottie Android 6.1 -Lottie goes multithreaded

Gabriel Peal
4 min readJul 16, 2023

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.

GRID Autosport on Android

Prior to Lottie 6.1 with the new asyncUpdates setting enabled, Lottie would call update and 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.

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.

--

--

Gabriel Peal

Open source maintainer of Lottie and Mavericks. Full stack at Watershed. Formerly Android at Tonal, Airbnb, and Android Auto at Google.