From bf591ff682f14db7ba7b3554897e9cdcf245da59 Mon Sep 17 00:00:00 2001 From: Jim Miller Date: Mon, 27 Jun 2011 19:33:27 -0700 Subject: Fix 4691563: Fix memory leak caused by Tweeners hanging onto references. This fixes a bug where the animations in MultiWaveView were keeping references to bitmaps and preventing them from being reclaimed during GC. The solution is two-fold: 1. When any given animation completes, it is now removed from the list of running animators objects. 2. When the client explicitly calls reset(), we release all references to animators and objects. Change-Id: Ice434ed1720fe4c253b9607ef61699d41f87f777 --- .../widget/multiwaveview/MultiWaveView.java | 5 +- .../internal/widget/multiwaveview/Tweener.java | 90 ++++++++++++++++------ 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java index 3e7b976aedb7..5b3510428f7b 100644 --- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java +++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java @@ -224,8 +224,8 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { /** * Animation used to attract user's attention to the target button. - * Assumes mChevronDrawables is an a list with an even number of chevrons filled with left - * followed by right chevrons. + * Assumes mChevronDrawables is an a list with an even number of chevrons filled with + * mFeedbackCount items in the order: left, right, top, bottom. */ private void startChevronAnimation() { final float r = mHandleDrawable.getWidth() / 2; @@ -442,6 +442,7 @@ public class MultiWaveView extends View implements AnimatorUpdateListener { mHandleDrawable.setX(mWaveCenterX); mHandleDrawable.setY(mWaveCenterY); mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); + Tweener.reset(); } @Override diff --git a/core/java/com/android/internal/widget/multiwaveview/Tweener.java b/core/java/com/android/internal/widget/multiwaveview/Tweener.java index 0cff00a514ab..bc8a62f2ca0b 100644 --- a/core/java/com/android/internal/widget/multiwaveview/Tweener.java +++ b/core/java/com/android/internal/widget/multiwaveview/Tweener.java @@ -18,25 +18,42 @@ package com.android.internal.widget.multiwaveview; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map.Entry; import android.animation.Animator.AnimatorListener; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.util.Log; class Tweener { private static final String TAG = "Tweener"; + private static final boolean DEBUG = false; - private Object object; ObjectAnimator animator; private static HashMap sTweens = new HashMap(); - public Tweener(Object obj, ObjectAnimator anim) { - object = obj; + public Tweener(ObjectAnimator anim) { animator = anim; } + private static void remove(Animator animator) { + Iterator> iter = sTweens.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + if (entry.getValue().animator == animator) { + if (DEBUG) Log.v(TAG, "Removing tweener " + sTweens.get(entry.getKey()) + + " sTweens.size() = " + sTweens.size()); + iter.remove(); + break; // an animator can only be attached to one object + } + } + } + public static Tweener to(Object object, long duration, Object... vars) { long delay = 0; AnimatorUpdateListener updateListener = null; @@ -77,32 +94,35 @@ class Tweener { // Re-use existing tween, if present Tweener tween = sTweens.get(object); + ObjectAnimator anim = null; if (tween == null) { - ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(object, + anim = ObjectAnimator.ofPropertyValuesHolder(object, props.toArray(new PropertyValuesHolder[props.size()])); - tween = new Tweener(object, anim); + tween = new Tweener(anim); sTweens.put(object, tween); + if (DEBUG) Log.v(TAG, "Added new Tweener " + tween); } else { - tween.animator.cancel(); - replace(props, object); + anim = sTweens.get(object).animator; + replace(props, object); // Cancel all animators for given object } if (interpolator != null) { - tween.animator.setInterpolator(interpolator); + anim.setInterpolator(interpolator); } // Update animation with properties discovered in loop above - tween.animator.setStartDelay(delay); - tween.animator.setDuration(duration); + anim.setStartDelay(delay); + anim.setDuration(duration); if (updateListener != null) { - tween.animator.removeAllUpdateListeners(); // There should be only one - tween.animator.addUpdateListener(updateListener); + anim.removeAllUpdateListeners(); // There should be only one + anim.addUpdateListener(updateListener); } if (listener != null) { - tween.animator.removeAllListeners(); // There should be only one. - tween.animator.addListener(listener); + anim.removeAllListeners(); // There should be only one. + anim.addListener(listener); } - tween.animator.start(); + anim.addListener(mCleanupListener); + anim.start(); return tween; } @@ -114,18 +134,40 @@ class Tweener { return Tweener.to(object, duration, vars); } - static void replace(ArrayList props, Object... args) { + // Listener to watch for completed animations and remove them. + private static AnimatorListener mCleanupListener = new AnimatorListenerAdapter() { + + @Override + public void onAnimationEnd(Animator animation) { + remove(animation); + } + + @Override + public void onAnimationCancel(Animator animation) { + remove(animation); + } + }; + + public static void reset() { + if (DEBUG) { + Log.v(TAG, "Reset()"); + if (sTweens.size() > 0) { + Log.v(TAG, "Cleaning up " + sTweens.size() + " animations"); + } + } + sTweens.clear(); + } + + private static void replace(ArrayList props, Object... args) { for (final Object killobject : args) { Tweener tween = sTweens.get(killobject); if (tween != null) { - if (killobject == tween.object) { - tween.animator.cancel(); - if (props != null) { - tween.animator.setValues( - props.toArray(new PropertyValuesHolder[props.size()])); - } else { - sTweens.remove(tween); - } + tween.animator.cancel(); + if (props != null) { + tween.animator.setValues( + props.toArray(new PropertyValuesHolder[props.size()])); + } else { + sTweens.remove(tween); } } } -- cgit v1.2.3-59-g8ed1b