Android动画内存泄漏原理

March 30, 2020

一、示例

ValueAnimator 持续输出从0到1000的整形值,然后反向输出1000到0数值,如此循环往复。该整形值转换为字符串设置到 TextView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class LeakActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_leak)

        val a = ValueAnimator.ofInt(0, 1000)
        a.duration = 1000
        a.repeatMode = ValueAnimator.REVERSE
        a.repeatCount = ValueAnimator.INFINITE
        // 传入AnimatorUpdateListener实现,此实现隐式持有Activity引用
        a.addUpdateListener { l -> textView.text = l.animatedValue.toString() }
        a.start()
    }
}

二、源码

2.1 类签名

首先关注 ValueAnimator 的父类,这里尤其关注类继承了接口 AnimationHandler.AnimationFrameCallback

1
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback

2.2 start()方法

然后看 start() 方法做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Override
public void start() {
    start(false);
}

// 初始化很多状态相关的布尔值,这些值都不是我们关心的,直接看方法第36行
private void start(boolean playBackwards) {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mSelfPulse = !mSuppressSelfPulseRequested;
    // Special case: reversing from seek-to-0 should act as if not seeked at all.
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            // Calculate the fraction of the current iteration.
            float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
            mSeekFraction = 1 - fraction;
        } else {
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;
    // Resets mLastFrameTime when start() is called, so that if the animation was running,
    // calling start() would put the animation in the
    // started-but-not-yet-reached-the-first-frame phase.
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    
    // 注意这里调用addAnimationCallback()方法
    // 此方法调用完,下面几行代码是实际开始动画的逻辑
    addAnimationCallback(0);

    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        // If there's no start delay, init the animation and notify start listeners right away
        // to be consistent with the previous behavior. Otherwise, postpone this until the first
        // frame after the start delay.
        startAnimation();
        if (mSeekFraction == -1) {
            // No seek, start at play time 0. Note that the reason we are not using fraction 0
            // is because for animations with 0 duration, we want to be consistent with pre-N
            // behavior: skip to the final value immediately.
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

上面提到的 addAnimationCallback() 方法看一下。

方法实参 this 就是 ValueAnimator 实例,作为 AnimationHandler.AnimationFrameCallback 接口的实现类,添加到 AnimationHandler.addAnimationFrameCallback() 方法内。

1
2
3
4
5
6
private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}

由于在下文这个 AnimationHandler 会再次出现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class AnimationHandler {
    // Internal per-thread collections used to avoid set collisions as animations start and end
    // while being processed.
    private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
            new ArrayMap<>();
    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
            new ArrayList<>();
    private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
            new ArrayList<>();

    // callback放入到列表中
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }

        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }
}

2.2 cancel()方法

文处实例代码没有调用动画的取消方法,所以直接看 ValueAnimator.cancel() 是如何释放资源的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public void cancel() {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }

    // 如果动画没启动,不需要取消而直接返回
    if (mAnimationEndRequested) {
        return;
    }

    // 只停止正在执行的动画监听器
    if ((mStarted || mRunning) && mListeners != null) {
        if (!mRunning) {
            // If it's not yet running, then start listeners weren't called. Call them now.
            notifyStartListeners();
        }
        
        // 回调所有注册的监听器
        ArrayList<AnimatorListener> tmpListeners =
                (ArrayList<AnimatorListener>) mListeners.clone();
        for (AnimatorListener listener : tmpListeners) {
            listener.onAnimationCancel(this);
        }
    }

    // 这里终止动画
    endAnimation();
}

endAnimation() 方法由 ValueAnimator 内部调用,结束动画时会从动画列表移除该动画,即 removeAnimationCallback()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void endAnimation() {
    if (mAnimationEndRequested) {
        return;
    }
  
    // 关注移除动画回调
    removeAnimationCallback();

    mAnimationEndRequested = true;
    mPaused = false;
    boolean notify = (mStarted || mRunning) && mListeners != null;
    if (notify && !mRunning) {
        // If it's not yet running, then start listeners weren't called. Call them now.
        notifyStartListeners();
    }
    mRunning = false;
    mStarted = false;
    mStartListenersCalled = false;
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    if (notify && mListeners != null) {
        ArrayList<AnimatorListener> tmpListeners =
                (ArrayList<AnimatorListener>) mListeners.clone();
        int numListeners = tmpListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            // 这里面会移除示例设置的AnimatorUpdateListener
            // 就是这个AnimatorUpdateListener持有的Activity引用
            // 只有本方法不调用,这个监听器就不会移除
            tmpListeners.get(i).onAnimationEnd(this, mReversing);
        }
    }
    // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
    mReversing = false;
    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                System.identityHashCode(this));
    }
}

endAnimation() 方法调用以下方法,前文 start() 方法分析出现的 AnimationHandler 再次出现。

1
2
3
4
5
6
private void removeAnimationCallback() {
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().removeCallback(this);
}

继续跳到这里获取单例

1
2
3
public AnimationHandler getAnimationHandler() {
    return AnimationHandler.getInstance();
}

可见 AnimationHandler 存放在 ThreadLocal 里面,就是主线程的 ThreadLocal 区域内。而 AnimationFrameCallback 保存在 AnimationHandler 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class AnimationHandler {

    private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime = new ArrayMap<>();
    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks = new ArrayList<>();
    private final ArrayList<AnimationFrameCallback> mCommitCallbacks = new ArrayList<>();

    // 所有注册的AnimationHandler按照线程分类,存放在线程的ThreadLocal区域内
    // 因为动画在主线程创建和播放,所以保存在主线程ThreadLocal
    // 而AnimationHandler实例包含的数据就是上面三个列表
    public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
    private boolean mListDirty = false;
    
    // AnimationHandler实例被获取之后
    // 会被调用后面的removeCallback(AnimationFrameCallback)方法
    public static AnimationHandler getInstance() {
        if (sAnimatorHandler.get() == null) {
            sAnimatorHandler.set(new AnimationHandler());
        }
        return sAnimatorHandler.get();
    }
    
    // 从三个列表移除回调
    public void removeCallback(AnimationFrameCallback callback) {
        // 如果页面退出但动画没有取消
        // 则callback隐式持有的Activity引用并一直保存在列表中
        mCommitCallbacks.remove(callback);
        mDelayedCallbackStartTime.remove(callback);
        int id = mAnimationCallbacks.indexOf(callback);
        if (id >= 0) {
            mAnimationCallbacks.set(id, null);
            mListDirty = true;
        }
    }
    
    .....
}

三、总结

总结内存泄漏路径:

  • ThreadLocal 在主线程持有 AnimationHandler 对象;
  • AnimationHandler 持有多个 AnimationFrameCallback 列表,列表负责不同功能;
  • AnimationFrameCallback 接口由 ValueAnimator 类实现,就是我们示例代码创建的 ValueAnimator
  • 创建 ValueAnimator 实例时,我们设置了监听器修改 textView 的值,该监听器隐式持有 Activity 实例;

若不结束或清除 ThreadLocalAnimationHandlerAnimationFrameCallback 回调,AnimationFrameCallback 就是 ValueAnimator,而 Activity 引用就被 ValueAnimator 的监听器永久间接持有造成内存泄漏;