美文网首页
LeakCanary检测内存泄露案例分析

LeakCanary检测内存泄露案例分析

作者: 馒Care | 来源:发表于2022-03-22 10:39 被阅读0次

    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<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
                new ArrayMap<>();
        private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
                new ArrayList<>();
        private final ArrayList<AnimationFrameCallback> 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<AnimationHandler> 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<AnimatorListener> tmpListeners =
                        (ArrayList<AnimatorListener>) 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<AnimatorListener> tmpListeners =
                        (ArrayList<AnimatorListener>) 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<AnimationHandler> 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检测内存泄露案例分析

          本文链接:https://www.haomeiwen.com/subject/eaxfjrtx.html