在项目开发中经常会用到倒计时这个功能,而Android也帮我们封装好了一个类CountDownTimer,给我们的开发带来了很大的方便;
CountDownTimer (long millisInFuture, long countDownInterval) 参数1,设置倒计时的总时间(毫秒) 参数2,设置每次减去多少毫秒
以App中获短信取验证码为例:
private Button btn;
private TextView vertifyView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initView();
}
private void initView(){
vertifyView =(TextView) findViewById(R.id.vertifyView);
btn =(Button) findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//1,请求后台...
//2,触发定时器刷新UI(启动代码最好放在请求后台回调成功之后)
timer.start();
}
});
}
private CountDownTimer timer = new CountDownTimer(10000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
vertifyView.setText((millisUntilFinished / 1000) + "秒后可重发");
}
@Override
public void onFinish() {
vertifyView.setEnabled(true);
vertifyView.setText("获取验证码");
}
};
ok~这样一个基本的CountDownTimer案例就完成了
四,存在的问题
CountDownTimer如果使用不当,常常会报空指针异常,甚至造成严重的内存泄漏
5.0源码:
public abstract class CountDownTimer {
/**
* Millis since epoch when alarm should stop.
*/
private final long mMillisInFuture;
/**
* The interval in millis that the user receives callbacks
*/
private final long mCountdownInterval;
private long mStopTimeInFuture;
/**
* boolean representing if the timer was cancelled
*/
private boolean mCancelled = false;
/**
* @param millisInFuture The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param countDownInterval The interval along the way to receive
* {@link #onTick(long)} callbacks.
*/
public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
/**
* Cancel the countdown.
*/
public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
}
/**
* 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;
}
/**
* Callback fired on regular interval.
* @param millisUntilFinished The amount of time until finished.
*/
public abstract void onTick(long millisUntilFinished);
/**
* Callback fired when the time is up.
*/
public abstract void onFinish();
private static final int MSG = 1;
// 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 if (millisLeft < mCountdownInterval) {
// 剩余时间小于一次时间间隔的时候,不再通知,只是延迟一下
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// 处理用户onTick执行的时间
long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
// 特殊情况:用户的onTick方法花费的时间比interval长,那么直接跳转到下一次interval
while (delay < 0) delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
}
从源码中我们可以看出,CountDownTimer的内部实现是采用Handler机制,通过sendMessageDelayed延迟发送一条message到主线程的looper中,然后在自身中收到之后判断剩余时间,并发出相关回调,然后再次发出message的方式。
这样的方式其实是有一定弊端的,那就是如果在Activity或者Fragment被回收时并未调用CountDownTimer的cancel()方法结束自己,这个时候CountDownTimer的Handler方法中如果判断到当前的时间未走完,那么会继续调用
sendMessageDelayed(obtainMessage(MSG), delay);
触发
onTick(millisLeft);
这一块空间始终无法被系统回收也就造成了内存泄漏问题。
1,将CountDownTimer设置为静态内部类
2,在配合DialogFragment使用时,如果在onFinish()方法调用了 dismiss()方法让弹框消失,记得 判断getFragmentManager是否为空
@Override
public void onFinish() {
if(getFragmentManager()!=null){
dismiss();
}
}
3,在使用CountDownTimer时,在宿主Activity或fragment生命周期结束的时候,记得调用timer.cancle()方法
@Override
public void onDestroy() {
if(timer!=null){
timer.cancel();
timer = null;
}
super.onDestroy();
}
遇到问题还是尽量先从控件的源码中寻找答案
Activity中的非静态内部类比较容易引起Actvity无法被回收的问题,特别是内部类的对象存在耗时或生命周期非常长的情形;
Activity或Fragment解决方法通常有两个步骤:
一是通过将内部类改成静态内部类,静态内部类不持有外部类的引用(当然这在对外部类的成员变量等操作时会麻烦一些);
二是在Activity或Fragment的生命周期的最后将该静态内部类操作的线程中断,或者将相关Handler中的消息进行移除。
其实在扩展到一般情形,外部类和内部类也是一样的,时刻注意他们的生命周期有助于发现内存无法释放的情况。
public class MainActivity extends Activity {
public Handler mHandler;
public MyRunnable mMyRunnable;
public final static int TIME_DELAY = 10*1000;
public final static String STR_TIP_WELCOME = "欢迎";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler();
mMyRunnable = new MyRunnable(this);
}
/**
* description:进行某种操作
*/
public void doSth(View view)
{
mHandler.postDelayed(mMyRunnable, TIME_DELAY);
}
/**
* description:在主线程中进行一些UI显示;更改为静态内部类
*/
public static class MyRunnable implements Runnable{
//引用当前的Activity为弱引用,并不影响GC回收
private WeakReference mActivityRef;
public MyRunnable(Activity pActivity){
mActivityRef = new WeakReference(pActivity);
}
@Override
public void run() {
Activity _activity = mActivityRef.get();
if(mActivityRef!=null&&mActivityRef.get()!=null) {
Toast.makeText(_activity,STR_TIP_WELCOME,Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//移除Handler中的消息
mHandler.removeCallbacksAndMessages(null);
}
}
CountDownTimer回调时在主线程;
而TimerTask回调到run()方法时是Timer子线程。
参考资料
https://blog.csdn.net/go_going/article/details/73123798
https://www.cnblogs.com/SomnusLove/p/4000500.html