计算机系统应用教程网站

网站首页 > 技术文章 正文

CountDownTimer的理解 count down time

btikc 2024-10-29 13:14:34 技术文章 4 ℃ 0 评论

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

kotlin:
object : CountDownTimer(30000, 1000) {

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

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

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

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

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

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

1.首先是License的说明:

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (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-2.0
 *
 * 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 30 second countdown in a text field:
 *
 * <div>
 * <div class="ds-selector-tabs"><section><h3 id="kotlin">Kotlin</h3>
 * <pre class="prettyprint lang-kotlin">
 * object : CountDownTimer(30000, 1000) {
 *
 *     override fun onTick(millisUntilFinished: Long) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000)
 *     }
 *
 *     override fun onFinish() {
 *         mTextField.setText("done!")
 *     }
 * }.start()
 * </pre>
 * </section><section><h3 id="java">Java</h3>
 * <pre class="prettyprint lang-java">
 * new CountDownTimer(30000, 1000) {
 *
 *     public void onTick(long millisUntilFinished) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
 *     }
 *
 *     public void onFinish() {
 *         mTextField.setText("done!");
 *     }
 * }.start();
 * </pre></section></div></div>
 *
 * 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(30000, 1000)表示整个倒计时为30秒,每10秒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) {
            ...
            }
        }
    };

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

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

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

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

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

12.如果上一步还有剩余时间,则调用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) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (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-2.0
 *
 * 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 30 second countdown in a text field:
 *
 * <div>
 * <div class="ds-selector-tabs"><section><h3 id="kotlin">Kotlin</h3>
 * <pre class="prettyprint lang-kotlin">
 * object : CountDownTimer(30000, 1000) {
 *
 *     override fun onTick(millisUntilFinished: Long) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000)
 *     }
 *
 *     override fun onFinish() {
 *         mTextField.setText("done!")
 *     }
 * }.start()
 * </pre>
 * </section><section><h3 id="java">Java</h3>
 * <pre class="prettyprint lang-java">
 * new CountDownTimer(30000, 1000) {
 *
 *     public void onTick(long millisUntilFinished) {
 *         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
 *     }
 *
 *     public void onFinish() {
 *         mTextField.setText("done!");
 *     }
 * }.start();
 * </pre></section></div></div>
 *
 * 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);
                }
            }
        }
    };
}

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表