Fix LottieTask leak (#2465)

I investigated leaks with `RememberLottieComposition` and `LottieAnimationView`, initially thinking that we might need to stop listening to LottieTask when a view or composition gets detached / removed.

However, after looking at a heap dump, I realized that the LottieTask was actually finished. It had delivered its result so it didn't have any reason to stay in memory.

This moved my suspicion to SynchronousQueue as Android has had issues with queues & worker threads on several occasions (see code for details).

Also updated the cached thread pool to create threads named "lottie".

Leaks:

```
┬───
│ GC Root: Thread object
│
├─ java.lang.Thread instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181913 objects
│    Thread name: 'pool-88-thread-2'
│    ↓ Thread<Java Local>
│            ~~~~~~~~~~~~
├─ java.util.concurrent.SynchronousQueue$TransferStack$SNode instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181909 objects
│    ↓ SynchronousQueue$TransferStack$SNode.match
│                                           ~~~~~
├─ java.util.concurrent.SynchronousQueue$TransferStack$SNode instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181908 objects
│    ↓ SynchronousQueue$TransferStack$SNode.item
│                                           ~~~~
├─ com.airbnb.lottie.LottieTask$LottieFutureTask instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181907 objects
│    ↓ LottieTask$LottieFutureTask.this$0
│                                  ~~~~~~
├─ com.airbnb.lottie.LottieTask instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181905 objects
│    ↓ LottieTask.failureListeners
│                 ~~~~~~~~~~~~~~~~
├─ java.util.LinkedHashSet instance
│    Leaking: UNKNOWN
│    Retaining 113 B in 5 objects
│    ↓ LinkedHashSet[element()]
│                   ~~~~~~~~~~~
├─ com.airbnb.lottie.compose.RememberLottieCompositionKt$await$2$2 instance
│    Leaking: UNKNOWN
│    Retaining 12 B in 1 objects
│    Anonymous class implementing com.airbnb.lottie.LottieListener
│    ↓ RememberLottieCompositionKt$await$2$2.$cont
│                                            ~~~~~
├─ kotlinx.coroutines.CancellableContinuationImpl instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181892 objects
│    ↓ CancellableContinuationImpl.delegate
│                                  ~~~~~~~~
├─ androidx.compose.ui.test.FrameDeferringContinuationInterceptor$FrameDeferredContinuation instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181888 objects
│    ↓ FrameDeferringContinuationInterceptor$FrameDeferredContinuation.continuation
│                                                                      ~~~~~~~~~~~~
├─ androidx.compose.ui.test.ApplyingContinuationInterceptor$SendApplyContinuation instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181887 objects
│    ↓ ApplyingContinuationInterceptor$SendApplyContinuation.continuation
│                                                            ~~~~~~~~~~~~
├─ com.airbnb.lottie.compose.RememberLottieCompositionKt$lottieComposition$1 instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181886 objects
│    Anonymous subclass of kotlin.coroutines.jvm.internal.ContinuationImpl
│    ↓ BaseContinuationImpl.completion
│                           ~~~~~~~~~~
├─ com.airbnb.lottie.compose.RememberLottieCompositionKt$rememberLottieComposition$3 instance
│    Leaking: UNKNOWN
│    Retaining 6.2 MB in 181885 objects
│    Anonymous subclass of kotlin.coroutines.jvm.internal.SuspendLambda
│    $context instance of com.squareup.ui.market.core.theme.MarketContextWrapper, wrapping activity com.squareup.ui.main.MainActivity with mDestroyed = true
│    ↓ RememberLottieCompositionKt$rememberLottieComposition$3.$context
│                                                              ~~~~~~~~
╰→ com.squareup.ui.market.core.theme.MarketContextWrapper instance
​     Leaking: YES (MarketContextWrapper wraps an Activity with Activity.mDestroyed true)
​     Retaining 6.2 MB in 181845 objects
​     mBase instance of com.squareup.ui.market.core.theme.MarketContextWrapper, wrapping activity com.squareup.ui.main.MainActivity with mDestroyed = true
```

```
┬───
│ GC Root: Thread object
│
├─ java.lang.Thread instance
│    Leaking: UNKNOWN
│    Retaining 8.1 MB in 257497 objects
│    Thread name: 'pool-38-thread-1'
│    ↓ Thread<Java Local>
│            ~~~~~~~~~~~~
├─ java.util.concurrent.SynchronousQueue$TransferStack$SNode instance
│    Leaking: UNKNOWN
│    Retaining 8.1 MB in 257493 objects
│    ↓ SynchronousQueue$TransferStack$SNode.match
│                                           ~~~~~
├─ java.util.concurrent.SynchronousQueue$TransferStack$SNode instance
│    Leaking: UNKNOWN
│    Retaining 8.1 MB in 257492 objects
│    ↓ SynchronousQueue$TransferStack$SNode.item
│                                           ~~~~
├─ com.airbnb.lottie.LottieTask$LottieFutureTask instance
│    Leaking: UNKNOWN
│    Retaining 8.1 MB in 257491 objects
│    ↓ LottieTask$LottieFutureTask.this$0
│                                  ~~~~~~
├─ com.airbnb.lottie.LottieTask instance
│    Leaking: UNKNOWN
│    Retaining 8.1 MB in 257489 objects
│    ↓ LottieTask.failureListeners
│                 ~~~~~~~~~~~~~~~~
├─ java.util.LinkedHashSet instance
│    Leaking: UNKNOWN
│    Retaining 101 B in 4 objects
│    ↓ LinkedHashSet[element()]
│                   ~~~~~~~~~~~
├─ com.airbnb.lottie.LottieAnimationView$1 instance
│    Leaking: UNKNOWN
│    Retaining 12 B in 1 objects
│    Anonymous class implementing com.airbnb.lottie.LottieListener
│    ↓ LottieAnimationView$1.this$0
│                            ~~~~~~
╰→ com.airbnb.lottie.LottieAnimationView instance
​     Leaking: YES (View.mContext references a destroyed activity)
​     Retaining 8.1 MB in 257476 objects
​     View not part of a window view hierarchy
​     View.mAttachInfo is null (view detached)
​     View.mWindowAttachCount = 1
​     mContext instance of flow.path.FlowPathContextWrapper, wrapping activity com.squareup.ui.main.MainActivity with mDestroyed = true
```
1 file changed
tree: d841cf2d0d2bc5f3141fb3bfe32eaecc40dc9559
  1. .github/
  2. .idea/
  3. After Effects Samples/
  4. app-benchmark/
  5. baselineprofile/
  6. benchmark/
  7. gifs/
  8. gradle/
  9. images/
  10. issue-repro/
  11. issue-repro-compose/
  12. lottie/
  13. lottie-compose/
  14. sample/
  15. sample-compose/
  16. snapshot-tests/
  17. .editorconfig
  18. .gitattributes
  19. .gitignore
  20. build.gradle
  21. CHANGELOG.md
  22. CHANGELOG_COMPOSE.md
  23. CODE_OF_CONDUCT.md
  24. deploy_snapshot.sh
  25. DESIGNER_NOTES.md
  26. gradle.properties
  27. gradlew
  28. gradlew.bat
  29. LICENSE
  30. lint.xml
  31. post_pr_comment.js
  32. README.md
  33. RELEASE.md
  34. settings.gradle
  35. sign.sh
  36. update-baseline-profiles.sh
  37. upload_release.sh
  38. version.sh
  39. versions.properties
README.md

Lottie for Android, iOS, React Native, Web, and Windows

Build Status

Lottie is a mobile library for Android and iOS that parses Adobe After Effects animations exported as json with Bodymovin and renders them natively on mobile!

For the first time, designers can create and ship beautiful animations without an engineer painstakingly recreating it by hand. They say a picture is worth 1,000 words so here are 13,000:

Sponsors

Lottie is maintained and improved on nights and weekends. If you use Lottie in your app, please consider sponsoring it to help ensure that we can continue to improve the project we love. Click the sponsor button above to learn more

Lead Sponsors

View documentation, FAQ, help, examples, and more at airbnb.io/lottie

Example1

Example2

Example3

Community

Example4

Download

Gradle is the only supported build configuration, so just add the dependency to your project build.gradle file:

dependencies {
  implementation 'com.airbnb.android:lottie:$lottieVersion'
}

The latest Lottie version is: lottieVersion

The latest stable Lottie-Compose version is: lottieVersion Click here for more information on Lottie-Compose.

Lottie 2.8.0 and above only supports projects that have been migrated to androidx. For more information, read Google's migration guide.

Contributing

Because development has started for Lottie Compose, Gradle, and the Android Gradle Plugin will be kept up to date with the latest canaries. This also requires you to use Android Studio Canary builds. Preview builds can be installed side by side with stable versions.