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

Commit 0698594d authored by Chet Haase's avatar Chet Haase
Browse files

Fix leak in LayoutTransition

Previously, an OnPreDrawListener was added to clean things up, including
removing the listener, after animations are started.
But if the view is detached before that listener is called, the
listener will remain on that ViewTreeObserver.

This change adds an OnAttachStateChangeListener to perform that cleanup
step when the view is detached, just in case.

Issue #20824645 Leak in LayoutTransition

Change-Id: I264812f8e02dda673789712ba83d208e87cdc5a4
parent d4474cb9
Loading
Loading
Loading
Loading
+51 −16
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * This class enables automatic animations on layout changes in ViewGroup objects. To enable
@@ -757,7 +758,7 @@ public class LayoutTransition {
        // reset the inter-animation delay, in case we use it later
        staggerDelay = 0;

        final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
        final ViewTreeObserver observer = parent.getViewTreeObserver();
        if (!observer.isAlive()) {
            // If the observer's not in a good state, skip the transition
            return;
@@ -790,21 +791,9 @@ public class LayoutTransition {
        // This is the cleanup step. When we get this rendering event, we know that all of
        // the appropriate animations have been set up and run. Now we can clear out the
        // layout listeners.
        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            public boolean onPreDraw() {
                parent.getViewTreeObserver().removeOnPreDrawListener(this);
                int count = layoutChangeListenerMap.size();
                if (count > 0) {
                    Collection<View> views = layoutChangeListenerMap.keySet();
                    for (View view : views) {
                        View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
                        view.removeOnLayoutChangeListener(listener);
                    }
                }
                layoutChangeListenerMap.clear();
                return true;
            }
        });
        CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent);
        observer.addOnPreDrawListener(callback);
        parent.addOnAttachStateChangeListener(callback);
    }

    /**
@@ -1499,4 +1488,50 @@ public class LayoutTransition {
                View view, int transitionType);
    }

    /**
     * Utility class to clean up listeners after animations are setup. Cleanup happens
     * when either the OnPreDrawListener method is called or when the parent is detached,
     * whichever comes first.
     */
    private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener,
            View.OnAttachStateChangeListener {

        final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap;
        final ViewGroup parent;

        CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) {
            this.layoutChangeListenerMap = listenerMap;
            this.parent = parent;
        }

        private void cleanup() {
            parent.getViewTreeObserver().removeOnPreDrawListener(this);
            parent.removeOnAttachStateChangeListener(this);
            int count = layoutChangeListenerMap.size();
            if (count > 0) {
                Collection<View> views = layoutChangeListenerMap.keySet();
                for (View view : views) {
                    View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
                    view.removeOnLayoutChangeListener(listener);
                }
                layoutChangeListenerMap.clear();
            }
        }

        @Override
        public void onViewAttachedToWindow(View v) {
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            cleanup();
        }

        @Override
        public boolean onPreDraw() {
            cleanup();
            return true;
        }
    };

}