android - How to make a guaranteed real-time stopwatch Android
问题描述
I'm trying to use a foreground service to implement a count up timer feature in my app, essentially a stopwatch. Below is the general idea of what I'm doing, which works well when the app is in the foreground, but not when the screen is off.
This is because handler.postDelayed()
is not guaranteed to be real time, and when the screen is off, it's far from it.
class TimerService: Service(), CoroutineScope {
private var currentTime: Int = 0
private val handler = Handler(Looper.getMainLooper())
private var runnable: Runnable = object : Runnable {
override fun run() {
if (timerState == TimerState.START) {
currentTime++
}
broadcastUpdate()
// Repeat every 1 second
handler.postDelayed(this, 1000)
}
}
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
// Call this to start the timer
private fun startCoroutineTimer() {
launch(coroutineContext) {
handler.post(runnable)
}
}
// Rest of the service class
...
}
My question is, what is the cannonical way to do something like this? I need to be able to guarantee, using a foreground service, that I can make an accurate stopwatch that is able to start, pause, resume, and stop.
I have the state broadcasting and everything else worked out already, but the actual timer part is where I'm stuck, and I can't seem to find a simple and efficient solution that is guaranteed to be accurate.
解决方案
First start with a different OS. There's an entire class of OSes called RTOS (real time OS). Linux (and thus Android) are not one. If you actually need realtime, linux is not an acceptable solution.
But let's say you don't actually need realtime, you just want higher accuracy. There's a few ways to easily improve your code.
The biggest thing is that your current code assumes the timer will go off once per second. Don't do that. Instead, keep track of the time when the timer starts. Each time the timer goes off, get the current time. The time elapsed is the delta. That way, any accumulated inaccuracies get wiped away each tick. That will also fix a lot of your screen off case, as the first update after screen on will update to the correct time elapsed.
private var timeStarted : Long = 0
private var timeElapsed : Long = 0
private var runnable: Runnable = object : Runnable {
override fun run() {
if (timerState == TimerState.START) {
timeElapsed = System.getCurrentTimeMillis() - timeStarted
}
broadcastUpdate()
// Repeat every 1 second
handler.postDelayed(this, 1000)
}
}
private fun startCoroutineTimer() {
timeStarted = System.getCurrentTimeMillis()
handler.post(runnable)
}
Also notice you don't need a coroutine to post to a handler. The handler takes care of multithreading, launching a coroutine there provides no value.
推荐阅读
- python - 删除网页抓取中的换行符
- asp.net-mvc - 如何在 Telerik 中动态显示隐藏工具栏
- wordpress - WordPress标签中的逗号
- spring-boot - 如何诊断/修复 Web 服务响应上的签名摘要验证失败?
- gradle - android_intent 报错先安装 Flutter 插件
- node.js - 使用 express-sslify 进行 https 重定向
- python - 带有 python 循环程序的项目
- callback - 为了方便,我们可以编辑回调函数 HAL_UART_TxCpltCallback 吗?
- mongodb - 如果 mongodb 聚合不使用 $lookup 的索引,为什么使用索引时我的性能会提高?
- android - 计算框架布局中的文本视图数