[Android, Kotlin] WorkManger로 매일 특정 시간에 백그라운드 작업 실행

 

WorkManager

 

백그라운드 작업에는 AlramManager, JobScheduler, JobDispatcher, WorkManager 등을 사용한다.

같은 시간에 동일한 작업을 진행하려면 AlramManager를 주로 사용하는데, 정확한 시간을 설정하는 것이 어려웠다.

AlarmManager 내의 정확하게 동작하는 여러 메서드를 사용해봤으나 항상 2-3분 정도 딜레이가 있었다.

 

정확한 시간에 동작하는 것은 가능하지만 즉시 실행되는 것을 보장하지 않았다.

이를 해결하기 위해서 WorkManger를 사용했다.

WorkManager의 장점은 프로세스 종료 여부와 관계없이 반드시 작업을 실행하는 것이었다. 

 

결과는 AlarmManager는 2~3분 정도 딜레이가 발생했지만, WorkManager는 정확한 시간에 동작했다.

정확한 시간을 보장하는 API는 AlarmManager였지만 여러 시도 끝에 WorkManager를 사용하기로 결심했다.

 

 

1. WorkManager의 구성 

 

[ Worker ]

 

- 클래스를 상속받고 백그라운드에서 실행하려는 코드를 doWork() 메서드 내에 정의한다 .

- sucess, failure, retry 등의 작업 상태를 통해서 결과를 알 수 있으며, 이를 통해서 동작을 결정할 수 있다.

 

[ WorkRequest ]

 

- Worker에 정의된 Task를 동작하기 위한 request를 나타낸다.

- 반복 여부, 제약 사항 등의 정보를 가진다. ( OneTimeWorkRequest or PeriodicWorkRequest )

 

[ WorkManager ]

 

- 실제로 WorkRequest를 스케줄링하고 실행하며 관리한다.

- 인스턴스를 받아서 WorkRequest를 내부 큐에 추가하여 실행한다.

 

 

2. WorkManager 구현

//coroutine work
implementation 'androidx.work:work-runtime-ktx:2.4.0'

 

Worker를 상속받은 클래스를 생성하고 Context와 WorkerParams를 생성자에게 넘기는 작업을 한다.

doWork() 메소드를 반드시 오버라이드 해야하며, 내부에 작업 코드를 작성한다 .

 

SchedulerWorker라는 Class를 선언했다. 

이 클래스는 Worker의 일부인 CoroutineWorker를 상속받는다.

 

class ScheduledWorker(context: Context, params:WorkerParameters)
    : CoroutineWorker(context,params)

 

내부에 doWork 함수를 오버라이드 한다.

coroutineScope 내부에서 원하는 코드를 실행하며 Result.success()를 반환한다.

 

    override suspend fun doWork(): Result = coroutineScope {

            // 실행
            if (Calendar.getInstance().get(Calendar.DAY_OF_MONTH)==1){
                applicationContext.sendBroadcast(monthBroadIntent())
            }else{
                applicationContext.sendBroadcast(dayBroadIntent())
            }
        Result.success()
    }

 

내가 원하는 방식은 매일 dayBroadIntent()를 정시에 실행시킨다.

해당 코드는 하루 데이터가 리셋 되었음을 사용자에게 알리며, 날짜 인덱스를 변경하는 코드이다.

 

만약에 Day가 1인 경우 새로운 달이 시작된 경우이므로 monthBroadIntent()를 정시에 실행시킨다.

해당 코드는 한달 데이터가 리셋 되었음을 사용자에게 알리며, 날짜 인덱스를 변경하고 Month 데이터를 갱신한다.

 

두 가지 함수가 day에 따라서 적절하게 실행된다.

sendBroadcast는 BroadCastReceiver와 NotificationManager를 통해서 사용자에게 알림 메시지를 보내는 기능을 한다. 

 

3. WorkRequest 구현

한 번 실행될 작업은 OneTimeWorkRequest로 작성하며, 여러 번 실행될 작업은 PeriodicWorkRequest로 작성된다.

PeriodicWorkRequest는 반복 주기 및 시간 단위를 인자로 넘기는데 최소값을 15분으로 설정해야 한다.

 

매일 정시에 Worker를 실행해야 했기에 PeriodicWorkRequest를 사용하려 했지만 시작 시간을 설정하는 것이 어려웠다.

따라서 OneTimeWorkRequest로 작성하며 반복 작업을 할 수 있도록 따로 설정했다.

 

WorkRequest는 어디에서 실행되어도 상관없지만, MainActivity에서 실행되도록 했다.

사용자가 로그인한 후에 서비스가 적용되어야 하므로 initWorkManager() 함수를

MainActivity에 선언하고 onCrate()에서 실행되도록 했다.

 

해당 함수는 OneTimeWorkRequest를 작성해서 위에서 제작한 doWork()를 실행하는 동작을 한다.

 

    private fun initWorkManager(){
        // One time work requeset -> scheduledWorker 호출
        val dailyWorkRequest = OneTimeWorkRequestBuilder<ScheduledWorker>()
            .setInitialDelay(getTimeUsingInWorkRequest(),TimeUnit.MILLISECONDS)
            .addTag("notify_day_by_day")
            .build()


        WorkManager.getInstance(applicationContext)
            .enqueue(dailyWorkRequest)

        WorkManager.getInstance(applicationContext)
            .getWorkInfoByIdLiveData(dailyWorkRequest.id)
            .observe(this@MainActivity){
                if(it.state.isFinished){ //종료 시 새로 등록
                    // 리셋 시도
                    try {
                        startActivity(Intent(applicationContext,LoginActivity::class.java))
                    }catch (_:Exception){
                    }
                }
            }
    }

 

OneTimeWorkRequest를 선언하고  enqueue를 통해서 WorkManager를 내부 큐에 등록한다.

이 후에 getWorkInfoByIdLiveData를 observe로 WorkManager의 state를 관찰한다.

 

getWorkInfoByIdLiveData는 생성한 Request의 id로 구분할 수 있으며,

state가 isFinished인 경우에 리셋을 시도한다.

 

로그인 액티비티를 실행시켜서 day정보와 사용자 정보를 리셋하며 인텐트를 실행하는 부분에 예외처리를 진행한다.

이제 앱 실행 시 WorkManager가 등록되어 백그라운드에서 동작한다.

 

https://developer.android.com/topic/libraries/architecture/workmanager?hl=ko 

 

앱 아키텍처: 데이터 영역 - WorkManager로 작업 예약 - Android 개발자  |  Android Developers

데이터 영역 라이브러리에 관한 이 앱 아키텍처 가이드를 통해 지속적인 작업 유형과 기능 등을 알아보세요.

developer.android.com

 

상세한 기능은 위 문서에서 확인할 수 있으며, Worker끼리 데이터 주고받기, 여러 작업 체이닝(합치기),

태그 추가 및 작업 관리, 제약 사항 추가 등 다양한 기능을 확인할 수 있다.

 

 

실행해본 결과 AlarmManager보다 정확한 시간에 요청 사항을 실행할 수 있었다.

AlarmManager가 정확한 시간에 사용하기 위해 설계된 API지만 WorkManager가 더 정확한 이유는 모르겠다.

 

## 충돌 정책과 작업 상태 관찰하기 관련 문서

 

https://developer.android.com/topic/libraries/architecture/workmanager/how-to/managing-work?hl=ko#conflict-resolution 

 

작업 관리  |  Android 개발자  |  Android Developers

작업 관리 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Worker와 WorkRequest를 정의했다면 작업을 큐에 추가하는 마지막 단계가 남아 있습니다. 작업을 큐에

developer.android.com