(七十六) CountDownTimer

(七十六) CountDownTimerdemo:https://github.com/happyjiatai/demo_csdn/tree/master/demo_76_countdowntimer1.CountDownTimer简介源码上是这么解释的:Scheduleacountdownuntilatimeinthefuture,with regularnotificationsoninterval…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

demo:https://github.com/happyjiatai/demo_csdn/tree/master/demo_76_countdowntimer

1. CountDownTimer简介

源码上是这么解释的:Schedule a countdown until a time in the future, with regular notifications on intervals along the way.翻译一下就是说设一个定时器,每隔固定间隔提醒一下。看了api,其实这个类在定时的时间到了以后也会通知一下用户。

 

2. CountDownTimer demo

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!。

(七十六) CountDownTimer

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

 

3. 源码分析

/**
 * 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()方法是抽象类,供调用方实现。

3.1 构造函数

指定定时时长和调用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;
    }

3.2 start()

计算一下停止时间,然后开始发送消息供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;
    }

3.3 cancel

停止消息处理

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

3.4 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是直接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[main,5,main] millisUntilFinished: 14981
07-27 20:15:05.753 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 13980
07-27 20:15:06.754 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 12979
07-27 20:15:07.758 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 11976
07-27 20:15:08.759 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 10975
07-27 20:15:09.760 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 9974
07-27 20:15:10.765 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 8969
07-27 20:15:11.766 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 7968
07-27 20:15:12.771 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 6963
07-27 20:15:13.773 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 5961
07-27 20:15:14.803 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 4930
07-27 20:15:15.149 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 14987
07-27 20:15:15.804 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 3929
07-27 20:15:16.149 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 13987
07-27 20:15:16.805 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 2928
07-27 20:15:17.149 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 12987
07-27 20:15:17.806 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 1927
07-27 20:15:18.151 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 11985
07-27 20:15:18.809 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 924
07-27 20:15:19.156 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] 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[main,5,main] millisUntilFinished: 9976
07-27 20:15:21.164 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 8973
07-27 20:15:22.166 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 7970
07-27 20:15:23.171 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 6966
07-27 20:15:24.175 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 5961
07-27 20:15:25.180 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 4957
07-27 20:15:26.181 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 3955
07-27 20:15:27.186 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 2951
07-27 20:15:28.190 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] millisUntilFinished: 1947
07-27 20:15:29.193 20938-20938/com.example.demo_76_countdowntimer D/countdowntimer: Thread[main,5,main] 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);
                }

 

4. 总结

使用CountDownTimer可以实现一些简单的固定间隔操作,定时结束后完成特定目标的需求(固定间隔定的是1s,但有可能是2s一刷新,取决于onTick执行时长),另外要注意其如果是在UI线程new出来的,那么onTick和onFinish不要执行耗时操作。CountDownTimer提前结束或者activity异常退出记得调用它的cancel方法,不然会有内存泄露。正常情况下使用handler请使用静态内部类加虚引用来规避内存泄露的风险。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/192344.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号