Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bf591ff6 authored by Jim Miller's avatar Jim Miller
Browse files

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
parent abb04eec
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -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
+66 −24
Original line number Diff line number Diff line
@@ -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<Object, Tweener> sTweens = new HashMap<Object, Tweener>();

    public Tweener(Object obj, ObjectAnimator anim) {
        object = obj;
    public Tweener(ObjectAnimator anim) {
        animator = anim;
    }

    private static void remove(Animator animator) {
        Iterator<Entry<Object, Tweener>> iter = sTweens.entrySet().iterator();
        while (iter.hasNext()) {
            Entry<Object, Tweener> 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,11 +134,34 @@ class Tweener {
        return Tweener.to(object, duration, vars);
    }

    static void replace(ArrayList<PropertyValuesHolder> 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<PropertyValuesHolder> 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(
@@ -130,4 +173,3 @@ class Tweener {
        }
    }
}
}