LeakCanary
集成方式
- 新版的集成方式相对于老版本更加方便。直接依赖就可以了,详见官网
泄露分析
-
这里根据Lobby实际案例来分析
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
- AnimationFrameCallback由ValueAnimator注册实现
- ValueAnimator由ParticleSystem创建并实例化,ParticleSystem设置了两个监听,AnimatorUpdateListener和AnimatorListener。这个监听是匿名内部类持有外部类的引用。随时可能导致内存泄露。
5.所以如果在结束当前操作的时候,没有解除ThreadLocal中AnimationHandler持有的AnimationFrameCallback,也就是ValueAnimator。那么这个引用就会一直存在。导致泄露