정확한 시간에 작업 예약하기

undefined·2023년 8월 1일
1

삽질로그

목록 보기
3/7

이번 프로젝트에서는 특정 시간에 정해진 작업을 수행하는 기능을 구현해야 했다.

가장 먼저 생각난 방법은 Workmanager와 AlarmManager였다.
오랜 시간동안 지속적으로 수행되는 작업이 아니다 보니 Service로 지속적으로 돌릴 필요는 없을 것 같았다.

먼저 AlarmManager 를 이용해서 기능을 구현했다.

class AlarmReceiver : BroadcastReceiver() {
    private var notificationManager: NotificationManager? = null

    @SuppressLint("UnsafeProtectedBroadcastReceiver")
    override fun onReceive(context: Context, intent: Intent) {
        if (notificationManager == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val name = "deajeon_station"
                val descriptionText = "Deajeon Station channel"
                val importance = NotificationManager.IMPORTANCE_HIGH
                val channel = NotificationChannel("deajeon_station", name, importance).apply {
                    description = descriptionText
                    enableVibration(true)
                }
                notificationManager =
                    context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                notificationManager?.createNotificationChannel(channel)
            }
        }
        Log.d(TAG, "알람이 울림 ${intent.action}")
        if (intent.action == "ALARM_RINGING") {

            val keyword = intent.getStringExtra("keyword")
            val formattedValue = intent.getStringExtra("formattedValue")
            if (keyword != null && formattedValue != null) {
                showNotification(context, keyword, formattedValue, SplashActivity::class.java)
            }

            val uiUpdateIntent= Intent("kr.deajeon.attendant.EVENT_UPDATE_RECYCLER_VIEW")
            LocalBroadcastManager.getInstance(context).sendBroadcast(uiUpdateIntent)
...
        }

그리고 디바이스가 종료되었다가 재 시작 되면 알람이 삭제되기 때문에 부팅이 완료되면 다시 알람을 등록하도록 했다.

		...
        else if (intent.action.equals("android.intent.action.BOOT_COMPLETED")) {
            Log.d(TAG, "onReceive BOOT_COMPLETED")
            CoroutineScope(Dispatchers.Default).launch {
                getInstance().performDailyTask()
            }
        }
        ...

비교적 잘 동작하는 것 처럼 보였으나, 앱이 완전히 종료되면 알람도 모두 사라졌다.😥 앱을 완전히 종료할 경우 메모리가 모두 지워지기 때문이라고 한다.

다음은 workManager를 이용하여 매일 자정에 작업을 수행하도록 작성한 코드이다.

		...
        val workRequest = PeriodicWorkRequestBuilder<DailyWorker>(1, TimeUnit.DAYS)
            .setInitialDelay(timeUntilMidnight, TimeUnit.MILLISECONDS)
            .setBackoffCriteria(BackoffPolicy.LINEAR,1,TimeUnit.SECONDS) // 시스템이 중단시켜도 자동으로 재개
            .build()

        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            "daily",
            ExistingPeriodicWorkPolicy.UPDATE,
            workRequest
        )
        ...

하지만 무엇때문인지 정확히 자정에 실행되지 않았다.
workmanager 는 시스템 과부하나 배터리 소모를 줄이기 위해 os에 의해 늦춰질 수 있다고 한다

그래서 시스템 브로드캐스트를 이용해보기로 했다. 시스템에서 전달하는 인텐트는 정확하게 동작 할 것이라고 생각했다.

그래서 브로드캐스트 리시버에 아래 내용을 추가했다.

		...
        else if (intent.action.equals(Intent.ACTION_DATE_CHANGED)) {
            Log.d(TAG, "onReceive ACTION_DATE_CHANGED")
            CoroutineScope(Dispatchers.Main).launch {
                getInstance().performDailyTask()
            }
        }
        ...

확인 결과 자정에 잘 동작하는것을 확인했다.🤗

2023-08-02 00:00:00.424 28543-28543 D 알람이 울림 android.intent.action.DATE_CHANGED

2023-08-04 00:00:00.117 5148-5148 D 알람이 울림 android.intent.action.DATE_CHANGED

adb로 등록된 알람목록을 확인해보면
잘 등록되었다!

정확한 시간에 수행하기 위해서 foreground service를 이용하는 방법도 있지만, 사용자가 알 필요가 없는 작업의 수행상태를 사용자에게 지속적으로 보여줄 필요가 없다고 생각했다. 때문에 이 방법은 고려하지 않았다.

profile
이것저것 하고 싶은 게 많은 병아리 개발자

1개의 댓글

comment-user-thumbnail
2023년 8월 1일

좋은 글이네요. 공유해주셔서 감사합니다.

답글 달기