Android 关于 CountDownTimer onTick() 倒计时不准确问题源码分析

本文分析了Android CountDownTimer在倒计时过程中出现的精度问题,包括每次onTick()的时间误差、剩余秒数显示不准确以及最后1秒的延迟过长。通过对API 25和26源码的分析,揭示了问题的原因并提出了改进方案,包括在创建CountDownTimer时增加额外的毫秒数以减少误差,以及在源码中调整逻辑以确保最后显示'0'秒。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一次写源码分析类博客,如有错误,欢迎讨论和指正~~ (^_^)

--------------------------------------

最近在写一个倒计时控件 CountdownView , 发现系统自带的 CountDownTimer onTick() 并不准确,当然,它的倒计时长度还是比较准确的。

本博客 demo 见: countdown

一、问题

CountDownTimer 使用比较简单,设置 5 秒的倒计时,间隔为 1 秒。

final String TAG = "CountDownTimer";

new CountDownTimer(5 * 1000, 1000) {
    @Override
    public void onTick(long millisUntilFinished) {
        Log.i(TAG, "onTick → millisUntilFinished = " + millisUntilFinished + ", seconds = " + millisUntilFinished / 1000);
    }

    @Override
    public void onFinish() {
        Log.i(TAG, "onFinish");
    }
}.start();

以 API 25 为例。即 app 的 build.gradle 中设置的编译版本是 25(后续会提到版本问题)。

compileSdkVersion 25


我们期待的效果是:“5-4-3-2-1-finish”或者“5-4-3-2-1-0”。这里,我认为 显示 0 和 finish 的时间应该是一致的,所以把 0 放在 onFinish() 里显示也可以。


先看一下运行效果图:

(demo 的 log 前面的毫秒数是手机当前系统时间戳)


打印日志可以看到有几个问题:

问题1. 每次 onTick() 都会有几毫秒的误差,并不是期待的准确的 "5000, 4000, 3000, 2000, 1000, 0"。

问题2. 多运行几次,就会发现这几毫秒的误差,导致了计算得出的剩余秒数并不准确,如果你的倒计时需要显示剩余秒数,就会发生 秒数跳跃/缺失 的情况(比如一开始从“4”开始显示——缺少“5”,或者直接从“5”跳到了“3”——缺少“4”)

问题3. 最后一次 onTick() 到 onFinish() 的间隔通常超过了 1 秒,差不多是 2 秒左右。如果你的倒计时在显示秒数,就能很明显的感觉到最后 1 秒停顿的时间很长


仔细看一下日志里标注的地方,如果你想直接看解决方案,可以直接滑到日志最下方,或者在顶部目录里选择最后一栏“三、终极解决”查看。


二、分析源码


(一)API 25 源码分析

查看 CountDownTimer 源码(API 25),


发现 start() 中计算的 mStopTimeInFuture(未来停止倒计时的时刻,即倒计时结束时间) 加了一个 SystemClock.elapsedRealtime() ,系统自开机以来(包括睡眠时间)的毫秒数,后文中以“系统时间戳”简称。

即倒计时结束时间为“当前系统时间戳 + 你设置的倒计时时长 mMillisInFuture ”,也就是计算出的相对于手机系统开机以来的一个时间。


继续往下看,多处用到了 SystemClock.elapsedRealtime() 。



在源码里添加 Log 打印看看。(直接在源码里修改是不会打印出来的,因为运行时不是编译的你刚刚修改的源码,而是手机里对应的源码。我复制了一份源码添加的 Log,见 demo 里的CountDownTimerCopyFromAPI25.java

String TAG = "CountDownTimer-25";
/**
 * Start the countdown.
 */
public synchronized final CountDownTimerCopyFromAPI25 start() {
    mCancelled = false;
    if (mMillisInFuture <= 0) {
        onFinish();
        return this;
    }

    //Add
    Log.i(TAG, "start → mMillisInFuture = " + mMillisInFuture + ", seconds = " + mMillisInFuture / 1000 );

    mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;

    //Add
    Log.i(TAG, "start → elapsedRealtime = " + SystemClock.elapsedRealtime());
    Log.i(TAG, "start → mStopTimeInFuture = " + mStopTimeInFuture);

    mHandler.sendMessage(mHandler.obtainMessage(MSG));
    return this;
}
// handles counting down
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {

    @Override
    public void handleMessage(Message msg) {

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

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

            //Add
            Log.i(TAG, "handleMessage → elapsedRealtime = " + SystemClock.elapsedRealtime());
            Log.i(TAG, "handleMessage → millisLeft = " + millisLeft + ", seconds = " + millisLeft / 1000 );

            if (millisLeft <= 0) {
                //Add
          
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值