-
Notifications
You must be signed in to change notification settings - Fork 12
(七十六) CountDownTimer
demo:github.com/happyjiatai/demo_csdn/tree/master/demo_76_countdowntimer
源码上是这么解释的:Schedule a countdown until a time in the future, with regular notifications on intervals along the way.翻译一下就是说设一个定时器,每隔固定间隔提醒一下。看了api,其实这个类在定时的时间到了以后也会通知一下用户。
demo(源码示例):
package com.example.demo_76_countdowntimer;
import android.os.Bundle; import android.os.CountDownTimer; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "countdowntimer"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView mTextField = findViewById(R.id.text); new CountDownTimer(15000, 1000) { public void onTick(long millisUntilFinished) { Log.d(TAG, "millisUntilFinished: " + millisUntilFinished); mTextField.setText("seconds remaining: " + millisUntilFinished / 1000); } public void onFinish() { Log.d(TAG, "onFinish() "); mTextField.setText("done!"); } }.start(); }
} 效果:会从14依次减少,最后变为done!。
log:
07-27 19:59:29.346 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 14992 07-27 19:59:30.346 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 13992 07-27 19:59:31.346 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 12992 07-27 19:59:32.348 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 11990 07-27 19:59:33.350 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 10988 07-27 19:59:34.354 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 9984 07-27 19:59:35.358 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 8980 07-27 19:59:36.360 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 7978 07-27 19:59:37.364 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 6974 07-27 19:59:38.369 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 5969 07-27 19:59:39.371 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 4967 07-27 19:59:40.375 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 3963 07-27 19:59:41.379 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 2959 07-27 19:59:42.382 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 1956 07-27 19:59:43.385 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: millisUntilFinished: 953 07-27 19:59:44.341 19686-19686/com.example.demo_76_countdowntimer D/countdowntimer: onFinish()
/**
* Schedule a countdown until a time in the future, with * regular notifications on intervals along the way. * * Example of showing a 30 second countdown in a text field: * * <pre class="prettyprint"> * new CountDownTimer(30000, 1000) { * * public void onTick(long millisUntilFinished) { * mTextField.setText("seconds remaining: " + millisUntilFinished / 1000); * } * * public void onFinish() { * mTextField.setText("done!"); * } * }.start(); * </pre> * * The calls to {@link #onTick(long)} are synchronized to this object so that * one call to {@link #onTick(long)} won't ever occur before the previous * callback is complete. This is only relevant when the implementation of * {@link #onTick(long)} takes an amount of time to execute that is significant * compared to the countdown interval. */
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 { 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); } } } };
} 源码实现其实一目了然,就是每隔某段时间发送一个消息回调onTick()方法,时间走完后回调onFinish()方法。
onTick()和onFinish()方法是抽象类,供调用方实现。
指定定时时长和调用onTick时间间隔
/** * @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; }
计算一下停止时间,然后开始发送消息供handler处理。
/** * 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; }
停止消息处理
/** * Cancel the countdown. */ public synchronized final void cancel() { mCancelled = true; mHandler.removeMessages(MSG); }
// 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是直接new处理的,说明是运行在新建线程的,demo是运行在主线程中,这意味着
onTick和onFinish不能执行耗时操作mHandler持有Context的引用,如果退出应用时不调用cancel,容易造成内存泄露两个操作可以证实内存泄露问题:
1.打开demo然后点击返回键,发现log仍然继续打印
2.点击返回键后再点击demo 图标,发现会有两个计时器在一起跑。
07-27 20:15:00.475 451-29656/? W/PackageManager: Failure retrieving resources for com.example.demo_76_countdowntimer: Resource ID #0x7f0a0000 07-27 20:15:04.752 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 14981 07-27 20:15:05.753 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 13980 07-27 20:15:06.754 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 12979 07-27 20:15:07.758 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 11976 07-27 20:15:08.759 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 10975 07-27 20:15:09.760 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 9974 07-27 20:15:10.765 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 8969 07-27 20:15:11.766 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 7968 07-27 20:15:12.771 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 6963 07-27 20:15:13.773 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 5961 07-27 20:15:14.803 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 4930 07-27 20:15:15.149 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 14987 07-27 20:15:15.804 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 3929 07-27 20:15:16.149 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 13987 07-27 20:15:16.805 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 2928 07-27 20:15:17.149 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 12987 07-27 20:15:17.806 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 1927 07-27 20:15:18.151 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 11985 07-27 20:15:18.809 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 924 07-27 20:15:19.156 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 10980 07-27 20:15:19.737 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: onFinish() 07-27 20:15:20.160 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 9976 07-27 20:15:21.164 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 8973 07-27 20:15:22.166 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 7970 07-27 20:15:23.171 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 6966 07-27 20:15:24.175 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 5961 07-27 20:15:25.180 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 4957 07-27 20:15:26.181 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 3955 07-27 20:15:27.186 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 2951 07-27 20:15:28.190 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 1947 07-27 20:15:29.193 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread millisUntilFinished: 943 07-27 20:15:30.139 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: onFinish() 接下来看下handeMessage是如何处理的:
1)加锁,考虑到多线程情况
2)cancel方法不是说只通过移除消息肯定会取消下一次操作的,这里加了个保险,如果cancel为true,则不继续执行。
3)计算下剩余时间,到了就回调onFinish()方法。
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime(); if (millisLeft <= 0) { onFinish(); }
4)时间没到的话,以执行onTick方法的时长为依据设定消息发送延迟。
如果剩余时长小于时间间隔,那么delay为剩余时长减去onTick方法的执行时长,如果小于0,则delay为0。
如果剩余时长大于时间间隔,那么delay为时间间隔减去onTick方法的执行时长,如果小于0则补时间间隔,直到大于0。
举个例子:
总时长15s,间隔1s(mCountdownInterval),执行onTick方法2.5s
millisLeft = 15s
先花费2.5s执行onTick方法,lastTickDuration=2.5s,剩余12.5s
走进if判断,由于millisLeft>mCountdownInterval,走进下面的else
1 - 2.5 = -1.5s,-1.5+1+1=0.5,下次消息将在0.5s后发出。
这样来看是3s发送一次消息。
例子举完总结一下,就是剩余时间不够一次间隔了,那么就从消息处理开始等剩余时间走完发送最后的消息;如果剩余时间够一次间隔,就从消息处理开始等n个间隔发送消息,n取决于onTick的执行时长是时间间隔的多少倍向上取整。
} 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); }
使用CountDownTimer可以实现一些简单的固定间隔操作,定时结束后完成特定目标的需求(固定间隔定的是1s,但有可能是2s一刷新,取决于onTick执行时长),另外要注意其如果是在UI线程new出来的,那么onTick和onFinish不要执行耗时操作。CountDownTimer提前结束或者activity异常退出记得调用它的cancel方法,不然会有内存泄露。正常情况下使用handler请使用静态内部类加虚引用来规避内存泄露的风险。