From 7236d32a249222c35db1ec20fd151a2d76557f7e Mon Sep 17 00:00:00 2001 From: Alexander Djenkov Date: Thu, 21 Feb 2019 10:49:35 +0200 Subject: [PATCH] fix(android-animations): reuse animatorSet to prevent high memory usage (#6930) --- .../ui/animation/animation.android.ts | 56 +++++++++++++------ tns-core-modules/ui/animation/animation.d.ts | 2 +- .../ui/animation/keyframe-animation.ts | 4 +- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/tns-core-modules/ui/animation/animation.android.ts b/tns-core-modules/ui/animation/animation.android.ts index 8359c9cd4..b536c6937 100644 --- a/tns-core-modules/ui/animation/animation.android.ts +++ b/tns-core-modules/ui/animation/animation.android.ts @@ -9,6 +9,7 @@ import { } from "../styling/style-properties"; import { layout } from "../../utils/utils"; +import { device } from "../../platform"; import lazy from "../../utils/lazy"; export * from "./animation-common"; @@ -92,6 +93,7 @@ export class Animation extends AnimationBase { private _propertyResetCallbacks: Array; private _valueSource: "animation" | "keyframe"; private _target: View; + private _resetOnFinish: boolean = true; constructor(animationDefinitions: Array, playSequentially?: boolean) { super(animationDefinitions, playSequentially); @@ -134,12 +136,18 @@ export class Animation extends AnimationBase { }); } - public play(): AnimationPromise { + public play(resetOnFinish?: boolean): AnimationPromise { + if (resetOnFinish !== undefined) { + this._resetOnFinish = resetOnFinish; + } + if (this.isPlaying) { return this._rejectAlreadyPlaying(); } - let animationFinishedPromise = super.play(); + if (this._animatorSet) { + return this._play(); + } this._animators = new Array(); this._propertyUpdateCallbacks = new Array(); @@ -156,21 +164,8 @@ export class Animation extends AnimationBase { this._animatorSet = new android.animation.AnimatorSet(); this._animatorSet.addListener(this._animatorListener); - if (this._animators.length > 0) { - if (this._playSequentially) { - this._animatorSet.playSequentially(this._nativeAnimatorsArray); - } - else { - this._animatorSet.playTogether(this._nativeAnimatorsArray); - } - } - if (traceEnabled()) { - traceWrite("Starting " + this._nativeAnimatorsArray.length + " animations " + (this._playSequentially ? "sequentially." : "together."), traceCategories.Animation); - } - this._animatorSet.setupStartValues(); - this._animatorSet.start(); - return animationFinishedPromise; + return this._play(); } public cancel(): void { @@ -188,6 +183,33 @@ export class Animation extends AnimationBase { return _resolveAnimationCurve(curve); } + private _play(): AnimationPromise { + const animationFinishedPromise = super.play(); + + if (device.sdkVersion <= "23") { + this._animatorSet = new android.animation.AnimatorSet(); + this._animatorSet.addListener(this._animatorListener); + } + + if (this._animators.length > 0) { + if (this._playSequentially) { + this._animatorSet.playSequentially(this._nativeAnimatorsArray); + } + else { + this._animatorSet.playTogether(this._nativeAnimatorsArray); + } + } + + if (traceEnabled()) { + traceWrite("Starting " + this._nativeAnimatorsArray.length + " animations " + (this._playSequentially ? "sequentially." : "together."), traceCategories.Animation); + } + + this._animatorSet.setupStartValues(); + this._animatorSet.start(); + + return animationFinishedPromise; + } + private _onAndroidAnimationEnd() { // tslint:disable-line if (!this.isPlaying) { // It has been cancelled @@ -197,7 +219,7 @@ export class Animation extends AnimationBase { this._propertyUpdateCallbacks.forEach(v => v()); this._resolveAnimationFinishedPromise(); - if (this._target) { + if (this._resetOnFinish && this._target) { this._target._removeAnimation(this); } } diff --git a/tns-core-modules/ui/animation/animation.d.ts b/tns-core-modules/ui/animation/animation.d.ts index 43edb439e..02e42000c 100644 --- a/tns-core-modules/ui/animation/animation.d.ts +++ b/tns-core-modules/ui/animation/animation.d.ts @@ -126,7 +126,7 @@ export type AnimationPromise = Promise & Cancelable; */ export class Animation { constructor(animationDefinitions: Array, playSequentially?: boolean); - public play: () => AnimationPromise; + public play: (resetOnFinish?: boolean) => AnimationPromise; public cancel: () => void; public isPlaying: boolean; public _resolveAnimationCurve(curve: any): any; diff --git a/tns-core-modules/ui/animation/keyframe-animation.ts b/tns-core-modules/ui/animation/keyframe-animation.ts index 2344499bd..49e6887bc 100644 --- a/tns-core-modules/ui/animation/keyframe-animation.ts +++ b/tns-core-modules/ui/animation/keyframe-animation.ts @@ -243,8 +243,10 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition { this._nativeAnimations.push(animation); } + const isLastIteration = iterations - 1 <= 0; + // Catch the animation cancel to prevent unhandled promise rejection warnings - animation.play().then(() => { + animation.play(isLastIteration).then(() => { this.animate(view, index + 1, iterations); }, (error: any) => { traceWrite(typeof error === "string" ? error : error.message, traceCategories.Animation, traceType.warn);