반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

코딩하기 좋은날

안드로이드 Timer 사용하기(fixed delay vs fixed rate) 본문

Android

안드로이드 Timer 사용하기(fixed delay vs fixed rate)

huiung 2021. 1. 10. 19:25
반응형

 

오랜만에 글을 쓰게 되었습니다!!

 

안드로이드에서 경과 시간을 체크하기 위해서 시간을 측정할 필요가 있었습니다.

 

또는 주기적인 작업에도 사용 할 수 있는데요. 저의 경우 일종의 스톱워치 기능을 구현하려고 했습니다.

 

Timer에서 초기 딜레이 값과, periode를 설정해주면 해당 주기마다 내부 동작이 실행 되는데,

 

코틀린의 경우 아래의 함수를 이용해서 타이머를 동작 시킬 수 있습니다. 저도 처음에는 timer(...)를 이용해서

시간을 측정하고 화면에 표시를 하였었는데 실제 스톱워치와 시간측정을 비교해보니 시간이 지날수록 오차가 발생하였습니다!!!(1분정도 측정하였을때 무려 5초나 오차가 났기 때문에 무시하기에는 큰 수치였습니다)

@kotlin.internal.InlineOnly
public inline fun timer(name: String? = null, daemon: Boolean = false, initialDelay: Long = 0.toLong(), period: Long, crossinline action: TimerTask.() -> Unit): Timer {
    val timer = timer(name, daemon)
    timer.schedule(initialDelay, period, action)
    return timer
}

 

위 함수를 보면 내부에서 timer의 schedule 함수를 호출하고 있는 것으로 보였습니다.

 

해당 함수의 구현체인 Timer.Java로 가보면 주석을 보던차에 fixed-delay execution 이라는 표현이 보입니다.

 

If an execution is delayed for any reason (such as garbage collection or other background activity), subsequent executions will be delayed as well.

요런 문장이 보이네요. 제가 period를 10ms로 설정을 하였는데, 오차가 발생하는 이유는 위와 같은 어떤 이유로 delay가 생겼고 그delay가 이후의 실행에도 영향을 미친다는 것으로 보입니다. 아래를 더 읽어보면,

 

In other words, it is appropriate for activities where it is more important to keep the frequency accurate

in the short run than in the long run. This includes most animation tasks, such as blinking a cursor at regular intervals. 

저런 동작들에 적합하다는 얘기들이 있는 것 같습니다. (해당 함수에 직접 가보시면 더 자세한 내용이 나와있습니다.) 

 

자 그럼 요녀석은 정확한 시간 측정을 하는데는 어려움이 있을 것 같습니다. 다시 Timer.kt로 돌아가 아래를 보니 이런 녀석이 있습니다. 내부에서 scheduleAtFixedRate라는 함수를 실행시키네요. 요녀석을 들어가보면

@kotlin.internal.InlineOnly
public inline fun fixedRateTimer(name: String? = null, daemon: Boolean = false, initialDelay: Long = 0.toLong(), period: Long, crossinline action: TimerTask.() -> Unit): Timer {
    val timer = timer(name, daemon)
    timer.scheduleAtFixedRate(initialDelay, period, action)
    return timer
}

 

이번에는 fixed-rate execution이라는 표현이 보입니다. 이름에서도 이미 fixed rate가 있죠.

 

If an execution is delayed for any reason (such as garbage collection or other background activity), two or more executions will occur in rapid succession to "catch up."

 

위의 fixed-delay와는 다른 문장이 있는것이 보입니다. 정확한 의미 해석은 사실 잘 안되는데, 해당 실행이 지연되어도 다른 작업들이 이에 상관없이 진행 되는 것처럼 보입니다. 아래를 보면

 

Fixed-rate execution is appropriate for recurring activities that are sensitive to <i>absolute</i> time, such as ringing a chime every hour on the hour, or running scheduled maintenance every day at a particular time

 

시간에 민감한 그런 작업들에 적절하다는 표현이 있습니다! 이녀석을 쓰면 뭔가 될것 같다는 느낌이 드네요.

 

val timer = fixedRateTimer(period = 1) {
            time++          
            val timeStr =  finalTimeText(time)
            activity?.runOnUiThread {
                binding.time.text = timeStr
            }
        }

1ms 마다 실행이 되며 경과시간을 textView에 표시하는 간단한 코드입니다. 실제로 이렇게 돌려보니 큰 오차는 발생하지 않는 것 같습니다.

 

finalTimetext는 혹시나 궁금하신 분이 있을까, 뭐 대충 이런식으로 하면 되겠죠.

fun finalTimeText(time: Int): String {

    var t = time

    val minute = t / (1000 * 60)
    t %= (1000*60)
    val sec = t / 1000
    val msec = t % 1000

    return "${minute}:${sec}:${msec}"
}

 

자 여기까지 하고, 이건 조금 다른 얘기지만 해결해야 할 약간의 이슈가 있습니다. 타이머가 돌던중에 유저가 화면을 나가버린다면? onPause나 onStop이 실행되는 경우가 있겠죠, Timer는 기본적으로 background Thread에서 돌아가기 때문에 유저가 다시 화면에 복귀한 경우 그만큼의 시간이 흘러있습니다. 이건 아마도 우리가 원하는 동작은 아니겠죠?

 

onPause나 onStop발생시 생성된 timer를 cancel하여 종료시켜줍니다.

 

override fun onPause() {
        super.onPause()
        timer?.cancel()        
        Log.d(TAG, "${fragmentName} Fragment onPause")
    }
    
override fun onResume() {
        super.onResume()
        stepTime()        
    }

 

그리고 onResume()에서 다시 실행시켜주면 되겠죠? stepTime()은 위의 타이머가 들어있는 함수입니다. time함수의 값만 유지되고 있다면 이로써 ms단위로 큰오차 없이 실행되는 스톱워치가 구현이 되는 듯합니다!

반응형