You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android平台实现具备实时性保障的秒表功能?

前台服务实现高精度秒表的规范方案

原有方案的核心缺陷是依赖handler.postDelayed()的执行间隔做累加计数,系统进入休眠状态时主线程消息队列调度会被冻结,自然会出现计时偏差甚至完全停止的问题。

核心实现逻辑

  • 完全取消每秒累加的计时逻辑,改为记录关键时间节点的时间戳,每次需要获取当前计时值时通过时间差计算,从根源避免调度偏差导致的误差
  • 每秒的UI刷新广播仅做通知用,不参与计时逻辑,哪怕广播延迟也不会影响最终计时结果
  • 仅需要存储3个核心变量即可支持启动、暂停、恢复、停止全操作:
    1. 记录计时累计时长(暂停时存储已经完成的计时时长)
    2. 记录本次启动/恢复的时间戳
    3. 计时状态标记(运行/暂停/停止)

改造后的代码实现

class TimerService: Service(), CoroutineScope {
    // 计时状态枚举
    enum class TimerState {
        STOP, START, PAUSE
    }

    // 累计已经走过的时长(单位ms,暂停时存入)
    private var elapsedTime: Long = 0L
    // 本次启动/恢复时的系统时间戳(单位ms)
    private var lastStartTime: Long = 0L
    // 当前计时状态
    private var timerState = TimerState.STOP

    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.IO + job

    // 每秒触发广播的runnable,仅做通知,不参与计时计算
    private val handler = Handler(Looper.getMainLooper())
    private val refreshRunnable = object : Runnable {
        override fun run() {
            broadcastUpdate()
            handler.postDelayed(this, 1000)
        }
    }

    // 对外暴露的启动方法
    fun startTimer() {
        if (timerState == TimerState.START) return
        lastStartTime = SystemClock.elapsedRealtime()
        timerState = TimerState.START
        handler.post(refreshRunnable)
        // 原有前台服务启动逻辑保留
    }

    // 对外暴露的暂停方法
    fun pauseTimer() {
        if (timerState != TimerState.START) return
        // 暂停时先把当前已经跑的时间算出来存到elapsedTime
        elapsedTime += SystemClock.elapsedRealtime() - lastStartTime
        timerState = TimerState.PAUSE
        handler.removeCallbacks(refreshRunnable)
        broadcastUpdate()
    }

    // 对外暴露的恢复方法,直接复用startTimer逻辑即可
    fun resumeTimer() = startTimer()

    // 对外暴露的停止方法
    fun stopTimer() {
        timerState = TimerState.STOP
        elapsedTime = 0L
        lastStartTime = 0L
        handler.removeCallbacks(refreshRunnable)
        broadcastUpdate()
        // 原有前台服务销毁逻辑保留
    }

    // 每次调用实时计算当前总计时秒数,无累计误差
    private fun getCurrentTotalSeconds(): Int {
        return if (timerState == TimerState.START) {
            val current = elapsedTime + (SystemClock.elapsedRealtime() - lastStartTime)
            (current / 1000).toInt()
        } else {
            (elapsedTime / 1000).toInt()
        }
    }

    private fun broadcastUpdate() {
        val currentTime = getCurrentTotalSeconds()
        // 原有广播发送逻辑,直接把currentTime发出去即可
        ...
    }

    // 其余服务原有逻辑保留
    ...
}

注意事项

  • 必须用SystemClock.elapsedRealtime()获取时间,不要用System.currentTimeMillis(),后者会跟随系统时间调整,修改系统时间会导致计时出现跳变
  • 休眠状态下如果觉得1秒刷新延迟太高,可以给前台服务加唤醒锁(WakeLock),但注意不需要一直持有,仅做刷新用的话不会太耗电。一般场景下不加也不影响计时准确性,只是UI刷新可能延迟,亮屏后会自动校正到正确时间
  • 前台服务必须按照安卓要求展示常驻通知,否则安卓12及以上系统会自动回收服务

内容的提问来源于stack exchange,提问作者Cameron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 22:36:03