Android CountDownTimer如何取消

CountDownTimer的理解

CountDownTimer是android开发常用的计时类,按照注释中的说明使用方法如下:

kotlin:
object : CountDownTimer, ) {

      override fun onTick(millisUntilFinished: Long) {
          mTextField.setText("seconds remaining: " + millisUntilFinished / )
      }
 
      override fun onFinish() {
          mTextField.setText("done!")
      }
}.start()
java
new CountDownTimer, ) {

     public void onTick(long millisUntilFinished) {
         mTextField.setText("seconds remaining: " + millisUntilFinished / );
     }

     public void onFinish() {
         mTextField.setText("done!");
     }
 }.start();

上文定义了一个倒计时类,每毫秒执行一次,将调用onTick方法,直到毫秒后结束,将调用onFinish方法。

CountDownTimer在aosp开源项目中的路径为:/frameworks/base/core/java/android/os/CountDownTimer.java

接下来我们来阅读源码进行理解:

1.首先是License的说明:

/*
 * Copyright (C)  The Android Open Source Project
 *
 * Licensed under the Apache License, Version  (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

没什么用,就不看了。

2.所属包名,也没什么用。

package android.os;

3.使用示例之前上文都说明白了,使用方法也很简单。需要注意的是后半段,意思是说,这个对象中的onTick方法是异步的, 前一次onTick没有完成前,本次的onTick是不会执行的。当onTick执行的时间超过时间间隔,就会出现没执行tick的问题。

/**
 * Schedule a countdown until a time in the future, with
 * regular notifications on intervals along the way.
 *
 * Example of showing a  second countdown in a text field:
 *
 * 
*

Kotlin

*
 * object : CountDownTimer, ) {
 *
 *     override fun onTick(millisUntilFinished: Long) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / )
 *     }
 *
 *     override fun onFinish() {
 *         mTextField.setText("done!")
 *     }
 * }.start()
 * 
*

Java

*
 * new CountDownTimer, ) {
 *
 *     public void onTick(long millisUntilFinished) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / );
 *     }
 *
 *     public void onFinish() {
 *         mTextField.setText("done!");
 *     }
 * }.start();
 * 
* * 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. */

4.接下来是CountDownTimer类的定义,首先是三个内部属性的定义:

第一个是mMillisInFuture,意思是当前时间到停止时间的毫秒数。然后是mCountdownInterval,表示间隔时间。mStopTimeInFuture虽然没说是什么,但是一看就知道是未来的停止时间。最后一个mCancelled表示这个计时器是否已经取消。

/**
     * 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;

5.接下来是构造函数,定义对象时,要提供从开始到结束的时间和ontick调用的时间间隔。比如 CountDownTimer, )表示整个倒计时为秒,每秒tick一次。

	/**
     * @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;
    }

6.结束方法,直接将 mCancelled置为ture,表示整个计时器结束,需要看到的是,将handle中的所有消息全移除了。也就是说,整个计时器是以handler为基础的。如果对handler不了解,需要先学习handler。MSG的值为1,后文有定义。

	/**
     * Cancel the countdown.
     */
    public synchronized final void cancel() {
        mCancelled = true;
        mHandler.removeMessages(MSG);
    }

7.开始方法,在定义好整个计时器对象后,随时可以调用start方法,开始计时,返回值为当前的计时器对象。方法首先将mCancelled置为false,换句话说,计时器对象是可以复用的。然后判断mMillisInFuture是否小于等于0。我们可以将这个值置为负数,那样就会直接调用onFinish然后就完事了。如果mMillisInFuture正常的话,就计算未来的停止时间mStopTimeInFuture,最后让handler发送一个类型为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;
    }

8.定义了两个虚拟方法和MSG类型。onTick的参数是millisUntilFinished,表示还有多少时间计时结束。别的没什么可说的。

	/**
     * 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;

9.最后是一个handler的定义,也是整个类最核心的部分,我们接下来详细看看。首先就直接new了一个Handler。这种方法是可能内存泄露的,不知道为什么谷歌认可了这种写法。

	// handles counting down
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            ...
            }
        }
    };

.synchronized (CountDownTimer.this)表示处理的代码是异步的,也印证了前文说的onTick触发是异步的。如果检测到计数器取消,则直接停止处理。

synchronized (CountDownTimer.this) {
                if (mCancelled) {
                    return;
                }
                ...
}

.首先获取剩余的时间,用停止时间减去
SystemClock.elapsedRealtime(),这个方法的返回值是设备boot启动的时间。如果剩余时间没了,就直接调用onFinish()。否则继续向下走。

								final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();

                if (millisLeft <= 0) {
                    onFinish();
                } else {
                    ...
                }

.如果上一步还有剩余时间,则调用onTick,同时还要记录tick用时时间,lastTickDuration明显是onTick执行前到执行后需要的时间。

接下来定义了一个delay。我们先看最后一行sendMessageDelayed(obtainMessage(MSG), delay);说明这个delay代表下一次handlemsg的时间点。

接下来我们看if条件,millisLeft < mCountdownInterval,判断的是剩余的时间和时间间隔。一般情况下,millisLeft都是远远大于mCountdownInterval的,只有在最后一次才tick结束后,会出现true。


也就是说,如图,如果当前时间点在最后一次onTick之前,millisLeft < mCountdownInterval都是false,只有在最后一次onTick和onFinish之间的时候,才会是true。

如果当前时间点在最后一次onTick之前,当计算delay的时候,此时我们在红线的尾部,那么下一次的onTick的delay时间就是两次onTick的间隔( mCountdownInterval)减去红线的长度(lastTickDuration),最后,如果delay小于0,说明onTick执行时间过长,则不停地增加 mCountdownInterval直到delay大于0,也就是说,如果onTick执行时间过长,则放弃过期的Tick调用。我们在使用的时候需要注意,如果TIck太久,onTick执行次数可能小于预期。

如果当前时间点在最后一次onTick和onFinish之间,我们此时关注的是onFinish的时间点,delay就是剩下的时间减去红线的长度(lastTickDuration),最后还是要注意,如果Tick时间过长,则立即发送消息(delay = 0),马上执行onFinish。我觉得此时onFinish会迟到。

										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);

最后,我们回顾下全文。

/*
 * Copyright (C)  The Android Open Source Project
 *
 * Licensed under the Apache License, Version  (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os;

/**
 * Schedule a countdown until a time in the future, with
 * regular notifications on intervals along the way.
 *
 * Example of showing a  second countdown in a text field:
 *
 * 
*

Kotlin

*
 * object : CountDownTimer, ) {
 *
 *     override fun onTick(millisUntilFinished: Long) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / )
 *     }
 *
 *     override fun onFinish() {
 *         mTextField.setText("done!")
 *     }
 * }.start()
 * 
*

Java

*
 * new CountDownTimer, ) {
 *
 *     public void onTick(long millisUntilFinished) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / );
 *     }
 *
 *     public void onFinish() {
 *         mTextField.setText("done!");
 *     }
 * }.start();
 * 
* * 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); } } } }; }
原文链接:,转发请注明来源!