最近有版本开发,加班到有空就想躺床上。总算有空余时间,来找找项目中的内存泄漏问题。
这是在登录页面出现的问题,出问题的是获取验证码倒计时控件出现的。其实这个内存泄漏很久之前就发现过,但是总是有时出现,有时有不出现。其实是因为在测试和开发环境中,直接使用的是默认的验证码,只是有时手抖会点到会出现这个。从错误信息中可以看出来,就是CountDownTimer导致的内存泄漏。
通过查找代码,发现VercCodeButton自定义view中,只开启了CountDownTimer,但是并没有调用停止CountDownTimer的方法
/**
* 60s倒计时
*/
private void initCountDown() {
downTimer = new CountDownTimer(AppConfig.COUNTDOWN, AppConfig.COUNTDOWNINTERVAL) {
@Override
public void onTick(long millisUntilFinished) {
isCountDown = true;
setText(millisUntilFinished / AppConfig.COUNTDOWNINTERVAL + "s");
setEnableColor(false);
}
@Override
public void onFinish() {
isCountDown = false;
setText("重新发送");
setEnableColor(true);
}
};
downTimer.start();
}
先看下countdownTimer中的入口
/**
* Start the countdown.
*/
public synchronized final CountDownTimer start() {
mCancelled = false;
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
11行可以看到,是通过handler来进行调度的,找到handler的代码
// handles counting down
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
synchronized (CountDownTimer.this) {
if (mCancelled) {
return;
}
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// take into account user's onTick taking time to execute
long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;
long delay;
if (millisLeft < mCountdownInterval) {
// just delay until done
delay = millisLeft - lastTickDuration;
// special case: user's onTick took more than interval to
// complete, trigger onFinish without delay
if (delay < 0) delay = 0;
} else {
delay = mCountdownInterval - lastTickDuration;
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;
}
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
可以看到,是通过创建了handler,从handleMessage方法中可以看到,判断的地方,在没有消耗完mStopTimeInFuture 值之前,每次都会重新发一条handler消息。但是,大部分时间,在登录页面都是在倒计时还没用完就会销毁页面,这时CountDownTimer内部还在发送消息,这样就会持有当前页面,当需要回收时,会出现引用数导致回收不了当前页面出现泄漏。就是常说的handler的内存泄漏。
其实要解决也很简单,在CountDownTimer内部已经提供了回收方法
/**
* Cancel the countdown.
*/
public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
}
我们只需要在页面销毁的时候调用CountDownTimer的cancel方法就能够避免这种问题
@Override
public void onDestroy() {
super.onDestroy();
if(mBtnVercCode!=null){
mBtnVercCode.cancel();
}
}