Merge pull request #499 from fatihergin/fix/ISSUE-423-stopwatch-is-not-accurate

replace handler with timer for stopwatch tasks
This commit is contained in:
Tibor Kaputa 2023-08-21 23:23:34 +02:00 committed by GitHub
commit 1754a7233c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 31 deletions

View File

@ -199,13 +199,17 @@ class StopwatchFragment : Fragment() {
private val updateListener = object : Stopwatch.UpdateListener { private val updateListener = object : Stopwatch.UpdateListener {
override fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) { override fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
updateDisplayedText(totalTime, lapTime, useLongerMSFormat) activity?.run {
updateDisplayedText(totalTime, lapTime, useLongerMSFormat)
}
} }
override fun onStateChanged(state: Stopwatch.State) { override fun onStateChanged(state: Stopwatch.State) {
updateIcons(state) activity?.run {
binding.stopwatchLap.beVisibleIf(state == Stopwatch.State.RUNNING) updateIcons(state)
binding.stopwatchReset.beVisibleIf(state != Stopwatch.State.STOPPED) binding.stopwatchLap.beVisibleIf(state == Stopwatch.State.RUNNING)
binding.stopwatchReset.beVisibleIf(state != Stopwatch.State.STOPPED)
}
} }
} }
} }

View File

@ -1,16 +1,16 @@
package com.simplemobiletools.clock.helpers package com.simplemobiletools.clock.helpers
import android.os.Handler
import android.os.Looper
import android.os.SystemClock import android.os.SystemClock
import com.simplemobiletools.clock.models.Lap import com.simplemobiletools.clock.models.Lap
import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.CopyOnWriteArraySet
private const val UPDATE_INTERVAL = 10L private const val UPDATE_INTERVAL = 10L
object Stopwatch { object Stopwatch {
private val updateHandler = Handler(Looper.getMainLooper()) private var updateTimer = Timer()
private var uptimeAtStart = 0L private var uptimeAtStart = 0L
private var totalTicks = 0 private var totalTicks = 0
private var currentTicks = 0 // ticks that reset at pause private var currentTicks = 0 // ticks that reset at pause
@ -27,7 +27,7 @@ object Stopwatch {
private var updateListeners = CopyOnWriteArraySet<UpdateListener>() private var updateListeners = CopyOnWriteArraySet<UpdateListener>()
fun reset() { fun reset() {
updateHandler.removeCallbacksAndMessages(null) updateTimer.cancel()
state = State.STOPPED state = State.STOPPED
currentTicks = 0 currentTicks = 0
totalTicks = 0 totalTicks = 0
@ -39,7 +39,7 @@ object Stopwatch {
fun toggle(setUptimeAtStart: Boolean) { fun toggle(setUptimeAtStart: Boolean) {
if (state != State.RUNNING) { if (state != State.RUNNING) {
state = State.RUNNING state = State.RUNNING
updateHandler.post(updateRunnable) updateTimer = buildUpdateTimer()
if (setUptimeAtStart) { if (setUptimeAtStart) {
uptimeAtStart = SystemClock.uptimeMillis() uptimeAtStart = SystemClock.uptimeMillis()
} }
@ -47,7 +47,7 @@ object Stopwatch {
state = State.PAUSED state = State.PAUSED
val prevSessionsMS = (totalTicks - currentTicks) * UPDATE_INTERVAL val prevSessionsMS = (totalTicks - currentTicks) * UPDATE_INTERVAL
val totalDuration = SystemClock.uptimeMillis() - uptimeAtStart + prevSessionsMS val totalDuration = SystemClock.uptimeMillis() - uptimeAtStart + prevSessionsMS
updateHandler.removeCallbacksAndMessages(null) updateTimer.cancel()
currentTicks = 0 currentTicks = 0
totalTicks-- totalTicks--
for (listener in updateListeners) { for (listener in updateListeners) {
@ -97,23 +97,26 @@ object Stopwatch {
updateListeners.remove(updateListener) updateListeners.remove(updateListener)
} }
private val updateRunnable = object : Runnable { private fun buildUpdateTimer(): Timer {
override fun run() { return Timer().apply {
if (state == State.RUNNING) { scheduleAtFixedRate(object : TimerTask() {
if (totalTicks % 10 == 0) { override fun run() {
for (listener in updateListeners) { if (state == State.RUNNING) {
listener.onUpdate( if (totalTicks % 10 == 0) {
totalTicks * UPDATE_INTERVAL, for (listener in updateListeners) {
lapTicks * UPDATE_INTERVAL, listener.onUpdate(
false totalTicks * UPDATE_INTERVAL,
) lapTicks * UPDATE_INTERVAL,
false
)
}
}
totalTicks++
currentTicks++
lapTicks++
} }
} }
totalTicks++ }, 0, UPDATE_INTERVAL)
currentTicks++
lapTicks++
updateHandler.postAtTime(this, uptimeAtStart + currentTicks * UPDATE_INTERVAL)
}
} }
} }

View File

@ -9,6 +9,7 @@ import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.Looper import android.os.Looper
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.simplemobiletools.clock.R import com.simplemobiletools.clock.R
import com.simplemobiletools.clock.extensions.getFormattedDuration import com.simplemobiletools.clock.extensions.getFormattedDuration
@ -18,7 +19,6 @@ import com.simplemobiletools.clock.helpers.Stopwatch
import com.simplemobiletools.clock.helpers.Stopwatch.State import com.simplemobiletools.clock.helpers.Stopwatch.State
import com.simplemobiletools.clock.helpers.Stopwatch.UpdateListener import com.simplemobiletools.clock.helpers.Stopwatch.UpdateListener
import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.helpers.isOreoPlus import com.simplemobiletools.commons.helpers.isOreoPlus
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
@ -54,15 +54,15 @@ class StopwatchService : Service() {
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy()
bus.unregister(this) bus.unregister(this)
Stopwatch.removeUpdateListener(updateListener) Stopwatch.removeUpdateListener(updateListener)
super.onDestroy()
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: StopwatchStopService) { fun onMessageEvent(event: StopwatchStopService) {
isStopping = true isStopping = true
stopForeground(true) stopForegroundService()
} }
private fun getServiceNotificationBuilder( private fun getServiceNotificationBuilder(
@ -98,17 +98,29 @@ class StopwatchService : Service() {
} }
private val updateListener = object : UpdateListener { private val updateListener = object : UpdateListener {
private val MIN_NOTIFICATION_UPDATE_INTERVAL = 500L
private var lastUpdateTime = 0L
override fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) { override fun onUpdate(totalTime: Long, lapTime: Long, useLongerMSFormat: Boolean) {
if (!isStopping) { if (!isStopping && shouldNotificationBeUpdated()) {
lastUpdateTime = System.currentTimeMillis()
updateNotification(totalTime) updateNotification(totalTime)
} }
} }
override fun onStateChanged(state: State) { override fun onStateChanged(state: State) {
if (state == State.STOPPED && isNougatPlus()) { if (state == State.STOPPED) {
stopForeground(STOP_FOREGROUND_REMOVE) stopForegroundService()
} }
} }
private fun shouldNotificationBeUpdated(): Boolean {
return (System.currentTimeMillis() - lastUpdateTime) > MIN_NOTIFICATION_UPDATE_INTERVAL
}
}
private fun stopForegroundService() {
ServiceCompat.stopForeground(this@StopwatchService, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
} }
} }