LeakCanary检测内存泄露案例分析

LeakCanary

集成方式

  • 新版的集成方式相对于老版本更加方便。直接依赖就可以了,详见官网

泄露分析

  • 这里根据Lobby实际案例来分析


    Lobby-ValueAnimator-Leak.jpg

    Lobby-Thread-Leak.jpg

1.截图所示,可以看到在实例化ParticleSystem的使用过程中出现了泄露
2.通过GC ROOT可以看出来真正泄露的地方在ValueAnimator的持有上
3.那么具体被持有方是谁,通过leakCanary可以看出来是AnimationHanler$mAnimationCallbacks
4.到这里,我们基本上可以确认泄露的点是在哪里。但还是需要继续分析,为什么会泄露
5.所以,这里需要分析一下ValueAnimatior源码

ValueAnimator

  • 以下分析均基于sdk31分析,不同版本可能略微有出入
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {

1.首先,我们可以看到ValueAnimator是继承了AnimationHandler.AnimationFrameCallback。这与上面leakCanary告诉我们的信息是匹配的
2.我们继续看start()方法做了什么

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(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);
            }
        }
    }

3.我们主要分析一下addAnimationCallback这个方法,因为leakCanary的引用链有显示这个地方

private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }

4.通过分析,我们可以看出参数 this 就是 ValueAnimator 实例,作为 AnimationHandler.AnimationFrameCallback 接口的实现类,添加到 AnimationHandler.addAnimationFrameCallback() 方法内

5.到这里整个引用链关系就很清晰了。也基本符合leakCanary的分析数据。
ValueAnimator#mListener#AnimationHandler#mAnimationCallbacks。

6.我们继续分析AnimationHandler源码

AnimationHandler

public class AnimationHandler {
    /**
     * Internal per-thread collections used to avoid set collisions as animations start and end
     * while being processed.
     * @hide
     */
    private final ArrayMap mDelayedCallbackStartTime =
            new ArrayMap<>();
    private final ArrayList mAnimationCallbacks =
            new ArrayList<>();
    private final ArrayList mCommitCallbacks =
            new ArrayList<>();
    private AnimationFrameCallbackProvider mProvider;

    private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

    public final static ThreadLocal sAnimatorHandler = new ThreadLocal<>();
    private boolean mListDirty = false;

    public static AnimationHandler getInstance() {
        if (sAnimatorHandler.get() == null) {
            sAnimatorHandler.set(new AnimationHandler());
        }
        return sAnimatorHandler.get();
    }

    /**
     * Register to get a callback on the next frame after the delay.
     */
    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));
        }
    }
}

1.我们先看addAnimationFrameCallback方法,这里会把所有的注册回调都放到AnimationFrameCallback中。
2.既然有注册监听,必然有移除监听的地方。我们继续看removeCallback

 /**
     * Removes the given callback from the list, so it will no longer be called for frame related
     * timing.
     */
    public void removeCallback(AnimationFrameCallback callback) {
        mCommitCallbacks.remove(callback);
        mDelayedCallbackStartTime.remove(callback);
        int id = mAnimationCallbacks.indexOf(callback);
        if (id >= 0) {
            mAnimationCallbacks.set(id, null);
            mListDirty = true;
        }
    }

3.通过上面可以知道,为了避免某种原因导致的内存泄露,我们必须在需要的时刻,移除相关监听。我们回到ValueAnimator

@Override
    public void cancel() {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }

        // If end has already been requested, through a previous end() or cancel() call, no-op
        // until animation starts again.
        if (mAnimationEndRequested) {
            return;
        }

        // Only cancel if the animation is actually running or has been started and is about
        // to run
        // Only notify listeners if the animator has actually started
        if ((mStarted || mRunning) && mListeners != null) {
            if (!mRunning) {
                // If it's not yet running, then start listeners weren't called. Call them now.
                notifyStartListeners();
            }
            ArrayList tmpListeners =
                    (ArrayList) mListeners.clone();
            for (AnimatorListener listener : tmpListeners) {
                listener.onAnimationCancel(this);
            }
        }

        endAnimation();

    }

4.这里主要看一下endAnimation。这里是停止动画的地方

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 tmpListeners =
                    (ArrayList) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                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));
        }
    }

4.注意源码的两个地方

  • removeAnimationCallback 移除动画
private void removeAnimationCallback() {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().removeCallback(this);
    }

从这里看出来需要获取AnimationHandler,这与上文提到的leakCanary告诉我们的信息也是一致的。

public AnimationHandler getAnimationHandler() {
        return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
    }
public final static ThreadLocal sAnimatorHandler = new ThreadLocal<>();
    private boolean mListDirty = false;

    public static AnimationHandler getInstance() {
        if (sAnimatorHandler.get() == null) {
            sAnimatorHandler.set(new AnimationHandler());
        }
        return sAnimatorHandler.get();
    }

通过获取AnimationHandler的单例可以看出,AnimationHandler是放在ThreadLocal里面的,这与图片中显示的引用链关系一致,Thread#threadLocal。同时,我们也可以看出AnimationFrameCallback 保存在 AnimationHandler 中。

  • tmpListeners.get(i).onAnimationEnd(this, mReversing) 移除监听,这里其实就是持有了Activity/Fragment的引用
总结

至此,我们就可以完整分析整个内存泄露的路径了
1.ThreadLocal持有了AnimationHandler
2.AnimationHandler持有了一堆AnimationFrameCallback

  1. AnimationFrameCallback由ValueAnimator注册实现
  2. ValueAnimator由ParticleSystem创建并实例化,ParticleSystem设置了两个监听,AnimatorUpdateListener和AnimatorListener。这个监听是匿名内部类持有外部类的引用。随时可能导致内存泄露。
    5.所以如果在结束当前操作的时候,没有解除ThreadLocal中AnimationHandler持有的AnimationFrameCallback,也就是ValueAnimator。那么这个引用就会一直存在。导致泄露

你可能感兴趣的:(LeakCanary检测内存泄露案例分析)