Skip to content

fix(android): prevent memory leak from reused CustomTransition animator#11270

Open
edusperoni wants to merge 1 commit into
mainfrom
fix/memory-leak-animator
Open

fix(android): prevent memory leak from reused CustomTransition animator#11270
edusperoni wants to merge 1 commit into
mainfrom
fix/memory-leak-animator

Conversation

@edusperoni

Copy link
Copy Markdown
Contributor

PR Checklist

What is the current behavior?

On Android, navigating forward/back with animations disabled leaks every visited page. CustomTransition exposes a single shared AnimatorSet (this.animatorSet) and returns it from onAppear/onDisappear. androidx clones the transition for each run but shares that field, and the framework attaches its own end listeners (Visibility$OverlayListener, etc.) to the returned AnimatorSet. For zero-duration ("none") navigation those listeners are not removed, so a reused CustomTransition accumulates one stale listener per navigation — each retaining the previous fragment and its page view. The leaked Java views in turn keep their linked JS objects alive, so the entire navigation history is held in memory forever (the chain roots through the live fragment's Fragment$AnimationInfoCustomTransitionAnimatorSet → stale listeners → previous fragments).

With animations enabled the animator actually runs and ends, androidx removes those listeners, and nothing leaks — which is why the leak only reproduces with animations off.

What is the new behavior?

CustomTransition now runs each transition on a per-invocation clone of the AnimatorSet instead of the shared instance, so the framework's end listeners land on a throwaway set and the shared template stays clean and collectable after the transition. It also releases the animated view reference (mView) when the animation ends. Pages and fragments are now garbage-collected after navigation regardless of whether animations are enabled.

CustomTransition exposed a single shared AnimatorSet (this.animatorSet)
and returned it from onAppear/onDisappear. androidx clones the transition
per run but shares this field, and the framework attaches its own end
listeners (Visibility$OverlayListener, etc.) to the returned AnimatorSet.
For zero-duration ("none") navigation those listeners are not removed, so
a reused CustomTransition accumulated one stale listener per navigation,
each retaining the previous fragment and its page view. With animations
disabled this leaked the entire navigation history; the leaked Java views
in turn kept their linked JS objects alive forever.

Run on a per-invocation clone of the AnimatorSet so framework listeners
land on a throwaway instance and the shared template stays clean, and
release the animated view (mView) when the animation ends.
@nx-cloud

nx-cloud Bot commented Jun 18, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 0482482

Command Status Duration Result
nx test apps-automated -c=android ✅ Succeeded 4m 5s View ↗
nx run-many --target=test --configuration=ci --... ✅ Succeeded <1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-06-18 17:25:17 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant