이번 포스팅에서는 Android Jetpack의 강력한 백그라운드 작업 관리 라이브러리인 WorkManager에 대해 알아보겠습니다. WorkManager는 장기 실행 작업을 안정적으로 예약할 수 있는 솔루션으로, Google Play 서비스 설치 여부에 관계없이 Ice Cream Sandwich 이후 모든 Android 버전에서 작동합니다. 이 글에서는 WorkManager에 대해 자세히 설명하겠습니다.
안드로이드는 리눅스 커널을 기반으로 개발되었지만 메모리 상의 비활성 데이터를 Swap Space로 이동시키는 공간 스왑(Swap Space)을 지원하지 않습니다. 그렇기에 OOM 킬러를 사용해서 프로세스를 강제로 종료시켜 메모리를 확보하는 역할을 합니다. 모든 프로세스는 액티비티 매니저가 부여한 자신의 oom_adj 점수를 가지고 있는데 이 점수는 앱의 상태(Foreground, Backgound, Service 등)로 결정됩니다. 이값은 클수록 OOM 킬러에게 정리당하기 쉽다는 특성이 있고 이것이 서비스를 위협하게 됩니다. 서비스는 별개의 프로세스에서 동작을 실행하며 이러한 특징으로 oom_adj 점수 높게 잡혀 강제 종료될 위험을 가지게됩니다. 이를 막기위해 Doze모드나 JobScheduler, JobDispatcher, JobIntentService등이 나오다 WorkManager가 나오게 되었습니다.

Worker 클래스는 백그라운드에서 수행하려는 작업을 정의하는 곳입니다. 자체 Worker 클래스를 만들고 doWork 메서드를 오버라이딩하여 작업을 정의합니다. 이 메서드는 백그라운드 스레드에서 비동기적으로 실행되며, 작업이 완료되면 Result를 반환합니다.
class MyWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) {
override fun doWork(): Result {
// 작업을 여기에 정의합니다.
return Result.success()
}
}
WorkRequest 클래스는 작업 실행 방법과 시기를 정의할 수 있습니다. WorkRequest는 빌더를 이용해 Worker에 대한 요청을 만들고, WorkManager 객체를 앱에서 가져와 해당 요청을 대기열에 추가할 수 있습니다. WorkRequest는 1회성 요청인지, 반복적으로 되는 정기적 요청인지를 선택할 수 있습니다.
val myWorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java).build()//일회성 요청
WorkManager.getInstance(applicationContext).enqueue(myWorkRequest)
작업을 예약할 때 특정 조건이 충족되었을 때만 작업이 실행되도록 설정할 수 있습니다. 예를 들어, 네트워크가 연결된 상태에서만 작업이 실행되도록 설정할 수 있습니다.
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)//네트워크가 연결됨
.build()
val constrainedWorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
.setConstraints(constraints)
.build()
WorkManager.getInstance(applicationContext).enqueue(constrainedWorkRequest
WorkManager는 beginWith로만 해당작업을 추가할수있는것은 아닙니다. beginUniqueWork메서드를 이용하여 해당작업에 고유한 키값을 생성합니다. 이때 해당작업이 있을때 어떻게 할것인지를 ExistingWorkPolicy를 통하여 설정할수있습니다. ExistingWorkPolicy에 이전의 요청을 무시하고 현재 요청으로 바꾸는 REPLACE, 이전요청을 유지하는 KEEP, 이전의 요청이후에 현재요청을 추가하는 APEEND등이 있습니다.
WorkManager.getInstance(applicationContext).beginUniqueWork(
"uniqueWorkName",
ExistingWorkPolicy.REPLACE, // REPLACE, KEEP, APPEND 중 선택
constrainedWorkRequest
).enqueue()
WorkInfo는 WorkRequest의 현재 상태에 관한 세부정보가 포함된 객체입니다.
작업의 상태(BLOCKED, CANCELLED, ENQUEUED, FAILED, RUNNING, SUCCEEDED 중에서)를 알수 있으며 크게3가지유형으로 세부정보를 가져옵니다. ID를 사용하여 WorkInfo를 가져오는 getWorkInfoByIdLiveData, 고유 체인 이름을 사용하여 WorkInfo 리스트를 가져오는 getWorkInfosForUniqueWorkLiveData, 태그를 사용하여 WorkInfo 리스트를 가져오는 getWorkInfosByTagLiveData가 있습니다. 이밖에도 메서드에 따라 LiveData말고 Flow로도 WorkInfo를 가져올수있습니다.
WorkManager가 처음부터 멀티스레딩을 염두에나온이유는 장기 실행 작업이 UI를 차단해서는 안된다는 것 때문입니다. WorkManager는 WorkRequest를 받게되면 해당 요청이 백그라운드 스레드에서 실행된다고 약속합니다. 그렇다면 WorkRequest를 만들때 사용되는 Worker클래스에서 스레딩코드강없다면 WorkManager는 어떤 스레드에서 해당 작업을 하게될까요? Worker클래스에는 startWork라는 메서드가 있습니다.

이는 Worker의 기본클래스인 ListenableWorker에 의해 제공되며 결국 선언된 작업을 실행하기 위해 WorkManager에 의해 호출됩니다. startWork에서는 스레딩이 일어납니다. Worker의 경우 startWork는 백그라운드 실행기(BackgroundExecutor)를 요청하고 doWork를 실행합니다. doWork에서 동기적으로 작성된 코드는 비동기화 됩니다. 이 비동기 실해기는 변경할수있습니다. 앱이 실해될때 가장 먼저 실행된는 Application클래스를 이용하여 해당사항을 설정할수있습니다.

이런식으로 Configuration.Provider의 getWorkManagerConfiguration을 오버라이드하여 비동기 실행이되는 스레드를 설정할수있습니다. 이때 Excutors를 이용하여 실행기를 선택할수있습니다. SingleThreadExcutor는 말그대로 한개의 스레드만지원하여 직렬로 해당작업을 처리하게됩니다. FixedThreadPool을 이용한다면 여러개의 작업을 병렬로 할수있게 됩니다.

이렇게 직접설정하지 않아도 WorkManager에서는 기본적으로 FixedThreadPool을 지원합니다. 이때 사용가능한 프로세스를 고려하여 2~4개의 ThreadPool을 지원합니다.
Kotlin은 코루틴을 지원합니다. WorkManager에서도 이를 고려하여 dependency에 설정한 WorkManager에 ktx를 추가해준다면 WorkManager에서도 코루틴을 지원하게됩니다. 사용방법은 간단합니다. 기존의 Worker대신 CoroutineWorker를 사용하면됩니다. 이에따라 doWork도 suspend가 추가되어 중단함수가 되고, 작업이 코루틴 디스패처에 의해 관리되게 됩니다. 디스패처를 바꾸고 싶다면 doWork작업에서 withContext를 이용하여 해당작업의 디스패처를 바꿀수 있습니다.
class MyApplication : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build()
}
}
코루틴말고도 RxJava를 지원하므로 해당 작업에맞게 사용하면 됩니다.

WorkManger는 즉시 실행할 필요가 없는 연기 가능한 작업을 예약할 수 있으므로 앱이 현재 활성화되어 있지 않은 경우 예약된 작업이 즉시 실행된다는 보장은 없습니다. 시스템에서 앱 대기 버킷에 배치했는지에 따라 예약된 작업이 연기될수있습니다. 앱 활성화 있을떄는 상관 없겠지만 사용자 프롬프트 없이 주기적으로 실행되도록 예정되어 있는경우, 앱상용 빈도에따라 작업이 연기될 수 있습니다.

WorkManager는 분명 좋은 Api이지만 모든작업에 사용할필요는 없습니다. 상황에따라 올바른 Api를 선택하는것이 중요한것 같습니다.
reference
https://www.youtube.com/watch?v=NtpgWjiXEfg&list=PLWz5rJ2EKKc_J88-h0PhCO_aV0HIAs9Qk&index=1
https://developer.android.com/develop/background-work/background-tasks/persistent?hl=ko
https://android-developer.tistory.com/16
https://velog.io/@hyejiseo-dev/Android-Background-%EC%B2%98%EB%A6%AC%EC%99%80-WorkManager