WorkManager

이석규·2024년 1월 31일
0

Workmanager는 무엇입니까

구글에서 만든, 백그라운드 작업을 예약하고 실행할 수 있는 라이브러리입니다.

특징

WorkManager는 언제 사용합니까

백그라운드 작업을 관리하는 라이브러리로써,
먼저 백그라운드 작업의 종류를 알아야겠습니다.

백그라운드 작업 종류

  • 즉시
  • 지연
  • 정시

단어의 뜻은 알겠지만, 정확히 분류를 해보겠습니다.

  1. 사용자가 애플리케이션과 상호작용 하는 동안 작업이 완료되어야 합니까?
    yes => 해당 작업은 "즉시" 실행되어야 합니다.
    no => 다음 질문으로 넘어가세요.

  2. 작업이 정확한 시간에 실행되어야 합니까?
    yes => 해당 작업은 "정시" 에 실행되어야 합니다.
    no => 해당 작업은 "지연"되어 실행됩니다.

백그라운드 작업 종류별 해결 방법

즉시

  • Coroutune : 사용자가 특정 범위를 벗어나거나 상호작용을 완료했을 때 종료되어야 하는 작업, 덕분에 ViewModel 같은 구성 요소에는 Coroutine 범위를 통해 애플리케이션 수명주기에 맞게 사용할 수 있습니다.
  • Foreground Service : 미디어 재생이나 활성 탐색 같은 특정 경우에는 Foreground Service를 사용하는 것이 좋습니다.
  • WorkManager : 사용자가 앱을 백그라운드로 전환하거나 기기를 다시 실행하더라도, 즉시 실행해야하고 지속적인 처리가 필요한 경우에는 WorkManager즉시 실행장기 실행을 사용하는 것이 좋습니다.

지연

  • WorkManager : 사용자와 상호작용에 직접 연결되지 않고, 향후에 언제든지 실행괼 수 있는 모든 작업은 지연될 수 있습니다. 이 때 WorkManager지연 실행을 사용하시는 것이 좋습니다. 이를 통해 앱이 종료되거나 기기가 다시 시작되더라도 지연될 수 있는 비동기 작업을 쉽게 예약할 수 있습니다.

정시

  • AlarmManager : 정확한 시점에 실행해야 하는 백그라운드 작업은 AlarmManager를 사용할 수 있습니다.

WorkManager의 상황별 백그라운드 작업 방법

WorkManger는 즉시 실행되거나 장기 실행 되거나 지연 실행되는 백그라운드 작업을 예약/시작하기 위해 사용합니다.

WorkManager는 왜 사용합니까

안드로이드가 버전을 올리며 백그라운드 작업에 대한 제약 사항이 생기면서 상당히 까다로워졌습니다. (초기에는 Service를 이용해서 작업하거나 BroadcastReceiver로 프로세스를 깨우는 것마저 가능했다고도 합니다.)


WorkManager는 구성 요소는 무엇이 있습니까

Worker

비동기로 처리할 작업을 정의합니다.

WorkRequest

Worker에 정의된 작업을 작동시키기 위한 request를 정의합니다.
request의 종류는 다음과 같습니다.

  • 일회성 작업 예약
    • +) 신속 처리 작업 예약
  • 주기적 작업 예약

신속 처리 작업 예약의 특성

  • 중요도: 신속 처리 작업은 사용자에게 중요하거나 사용자가 시작한 작업에 적합합니다.
  • 속도: 신속 처리 작업은 즉시 시작되어 몇 분 안에 끝나는 짧은 작업에 가장 적합합니다.
  • 할당량: 포그라운드 실행 시간을 제한하는 시스템 수준 할당량에 따라 신속 처리 작업의 시작 가능 여부가 결정됩니다.
  • 전원 관리: 절전 모드, 잠자기와 같은 전력 관리 제한사항은 신속 처리 작업에 영향을 미칠 가능성이 적습니다.
  • 지연 시간: 시스템의 현재 워크로드로 처리가 가능한 경우 시스템은 신속 처리 작업을 즉시 실행합니다. 즉, 신속 처리 작업은 지연 시간에 민감하며 나중에 실행되도록 예약할 수 없습니다.

WorkManager

Worker와 WorkRequest를 정의했다면, 작업을 큐(WorkManager)에 추가해야 합니다.(실행하기 위해)


WorkManager는 어떻게 작동합니까

Worker

Abstract class인 Worker 클래스를 상속하고, doWork() 메서드를 상속하여 작업을 정의합니다.
doWork()가 반환하는 Result는 작업의 성공 여부를 의미하고, 결과에 따라 success(), failure(), retry()를 반환합니다.

class UploadWorker(appContext: Context, workerParams: WorkerParameters):
       Worker(appContext, workerParams) {
   override fun doWork(): Result {

       // Do the work here--in this case, upload the images.
       uploadImages()

       // Indicate whether the work finished successfully with the Result
       return Result.success()
   }
}

WorkRequest

일회성 작업 예약 (One-time work)

간단한 작업에는 정적 메서드 from을 사용합니다.

WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

더 복잡한 작업에는 빌더를 사용합니다.

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

+) 신속 처리 작업 예약 (Expedited work)

WorkManager 2.7.0에는 신속 처리 작업의 개념이 도입되었습니다.
이를 통해 WorkManager는 시스템에 더 효율적인 리소스 액세스 제어 권한을 부여하면서 중요한 작업을 실행할 수 있습니다.

setExpedited() 를 호출하여 신속 처리 작업을 사용하는 WorkRequest가 최대한 빨리 실행되도록 선언할 수 있습니다.

val request = OneTimeWorkRequestBuilder
    .setInputData(inputData)
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build();

앱이 실행 할당량에 도달할 경우 신속 처리 작업을 어떻게 처리할지 제어할 수 있습니다. setExpedited()에 전달한 인자로 이를 제어할 수 있습니다.

OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST : 작업이 일반 작업 요청으로 실행됩니다.
OutOfQuotaPolicy.DROP_WORK_REQUEST : 할당량이 충분하지 않으면 요청이 취소됩니다.

주기적 작업 예약 (Periodic work)

경우에 따라 앱에서는 특정 작업을 주기적으로 실행해야 할 수 있습니다. (이 때 설정할 수 있는 최소 주기는 15분 입니다.)
예를 들어 주기적으로 데이터를 백업하거나 최신 콘텐츠를 앱에 다운로드하거나 로그를 서버에 업로드해야 할 수 있습니다.

다음은 한 시간 간격으로 예약됩니다.

val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

작업을 실행할 수 있는 가변 간격을 설정할 수 있습니다. repeatInterval-flexInterval에 실행되는 주기적 작업입니다.

val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()

작업 제약 조건 걸기

제약 조건은 최적의 조건이 충족될 때, 작업이 지연되도록 합니다.

NetWorkType : 작업을 실행하는 데 필요한 네트워크 유형을 제한합니다. 예: Wi-Fi(UNMETERED)
BatteryNotLow : true로 설정하면 배터리 부족 모드인 경우 작업이 실행되지 않습니다.
RequiresCharging : true로 설정하면 기기가 충전 중일 때만 작업이 실행됩니다.
DeviceIdle : true로 설정하면 작업이 실행되기 전에 기기가 유휴 상태(작업이 가능한 상태)여야 합니다. 이는 사용자 기기에서 활발하게 실행되는 다른 앱의 성능에 부정적인 영향을 줄 수 있는 배치 작업을 실행하는 데 유용합니다.
StorageNotLow : true로 설정하면 사용자 기기의 저장공간이 너무 부족한 경우 작업이 실행되지 않습니다.

일련의 제약 조건을 만들고 이를 일부 작업과 연결하려면 Contraints.Builder()를 사용하여 Constraints 인스턴스를 만들어 WorkRequest.Builder()에 할당합니다.
다음은 사용자 기기가 충전 중이고, Wi-Fi에 연결되어 있을 때만 실행되는 작업 요청을 빌드합니다.


val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

지연된 작업

작업이 모든 조건을 만족하면 즉시 실행할 수 있지만, 이를 하지 않으려면 작업을 지연할 수도 있습니다.
다음은 작업이 큐에 추가되고, 최소 10분 후에 실행되도록 설정합니다.


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

PeroidicWorkRequest에서도 이를 설정할 수 있지만, 이 경우 주기적 작업의 첫번째 실행만 지연됩니다.

재시도 및 백오프 정책

작업을 재시도해야하는 경우, Worker에서 Result.retry()를 반환하면 됩니다.

  • 백오프 지연은 첫번째 시도 후 작업을 다시 시도하기 전에 대기해야 하는 최소 시간을 지정합니다. 이 값은 10초 미만일 수 없습니다.
  • 백오프 정책은 다음 백오프 지연이 시간 경과에 따라 증가하는 방식을 정의합니다.
    WorkManager에서는 LINEAR, EXPOTENTIOAL(default)을 제공합니다.

다음과 같이 설정합니다.


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()
  • BackoffPolicy.LINEAR → 20, 30, 40…
  • BackoffPolicy.EXPONENTIAL → 20, 40, 80…

작업에 태그 지정

고유 식별자를 작업에 지정할 수 있습니다. 이를 통해 관찰하거나 취소할 수 있습니다.

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

ListenableWorker.getTags()를 통해 Worker 클래스에서 태그 집합을 가져올 수 있습니다.

입력 데이터 할당

작업을 실행하는데에 필요한 데이터를 할당할 수 있습니다.

입력값은 Data객체에 키-쌍 값으로 저장되고, 작업 요청에서 설정할 수 있습니다.
작업 실행 시 작업에 Data를 제공합니다.
Worker 클래스는 Worker.getInputData()를 호출하여 입력 인수에 액세스 할 수 있습니다.


// Define the Worker requiring input
class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       val imageUriInput =
           inputData.getString("IMAGE_URI") ?: return Result.failure()

       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf(
       "IMAGE_URI" to "http://..."
   ))
   .build()

체인된 작업에서 데이터를 주고 받는 내용은 다음에서 확인해 볼 수 있습니다.

WorkManager

WorkManager의 enqueue() 메서드를 호출하여 실행하고자 하는 WorkRequest를 전달합니다.

정의

val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)

종류

enqueue() 메서드는 3가지 종류가 있습니다.

  • enqueue() -> 기본적인 방법입니다.

다음 두 가지 방식은 고유 작업을 위한 enqueue() 입니다.

  • enqueueUniqueWork() -> 일회성 작업을 위한 방법입니다.
  • enqueuePeriodicWork() -> 주기성 작업을 위한 방법입니다.

고유 작업

고유 작업은 특정 이름의 작업이 한 번에 하나만 있도록 보장하는 개념입니다.

다음의 인수를 허용합니다.
uniqueWorkName - 작업 요청을 고유하게 식별하는 String 값 입니다.
work - 예약할 WorkRequest 입니다.
existingworkPolicy - 고유 이름이 있는 작업 체인이 아직 완료 되지 않은 경우, WorkManager에게 알려줄 충돌 해결 정책 Enum입니다.

충돌 해결 정책

일회성 작업의 충돌 해결 정책은 다음과 같습니다.
REPLACE : (충돌 시)기존 작업을 취소하고, 새 작업으로 대체합니다.
KEEP : 기존 작업을 유지하고, 새 작업을 무시합니다.
APPEND : 새 작업을 기존 작업의 끝에 추가합니다. 이것은 새 작업을 기존 작업에 체이닝하는 것과 같습니다.
APPEND_OR_REPLACE : APPEND와 유사하게 작동하지만, 새 작업이 체인된 기존 작업의 작업 상태에 의존하지 않습니다. 기존 작업이 CANCLLED 또는 FAILED 라도 새 작업은 계속 작업합니다.

주기성 작업은 위의 4가지 정책 중, REPLACE와 KEEP만 제공합니다.

작업 관찰

작업을 큐에 추가한 후에는 언제든지, name/id/tag로 WorkManager에 쿼리하여 작업 상태를 확인할 수 있습니다.


// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>

쿼리는 WorkInfo 객체의 ListenableFuture를 반환하고, 여기에는 id, tag, 현재 state, Result.success(outputData)를 통해 설정된 출력 데이터도 포함됩니다.

각 메서드의 LiveData 변형을 사용하면 리스너를 등록하여 WorkInfo를 관찰할 수 있습니다.
다음은 일부 작업이 완료될 때, 사용자에게 메시지를 표시하려면 다음과 같습니다.


workManager.getWorkInfoByIdLiveData(syncWorker.id)
               .observe(viewLifecycleOwner) { workInfo ->
   if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
       Snackbar.make(requireView(),
      R.string.work_completed, Snackbar.LENGTH_SHORT)
           .show()
   }
}

WorkManager 2.4.0 이상에서는 WorkQuery 객체를 사용하여 복잡한 쿼리를 지원합니다.
다음은 태그가 'syncTag'이고 FAILED 또는 CANCELLED 상태이며 고유 작업 이름이 'preProcess' 또는 'sync'인 작업을 모두 찾을 방법을 보여줍니다.


val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)

작업(Work) 취소 및 중지

큐에 추가한 작업을 취소하도록 요청할 수도 있습니다. 작업을 관찰할 때와 마찬가지로 name/id/tag로 WorkManager에 요청할 수 있습니다.


// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag : cancelAllWorkByTag는 주어진 태그가 있는 모든 작업을 취소합니다.
workManager.cancelAllWorkByTag("syncTag")

작업이 이미 완료되었다면, 아무 변화 없습니다.
완료되지 않았다면, 작업의 상태가 CANCELLED로 변경되고, 작업이 중지됩니다. 작업에 종속된 모든 WorkRequest가 함께 중지됩니다.

현재 진행 중인 작업은 ListenableWorker,onStopped() 호출을 수신합니다.

실행 중인 작업자(Worker) 중지

onStopped() 콜백

작업이 중지되면, WorkManager에서는 ListenableQorker.onStopped()를 호출합니다.
해당 메서드를 재정의하여 보유하고 있는 모든 리소스를 해제해야 합니다.

isStopped() 속성

ListenableQorker.isStopped()를 통해 작업자가 이미 중지되었는지 확인할 수 있습니다.

작업 체이닝 및 추가적인 내용

WorkManager를 사용하면 여러 종속 작업을 지정하고 실행 순서를 정의하는 작업 체인을 만들고 큐에 추가할 수 있습니다.

작업 체인을 만들려면

  1. 각각 WorkContinuation 인스턴스를 반환하는 WorkManager.beginWith(OneTimeWorkRequest) 또는
    WorkManager.beginWith(List<OneTimeWorkRequest>)를 사용하면 됩니다.

  2. 그런 다음 WorkContinuation을 사용하여 then(OneTimeWorkRequest) 또는 then(List<OneTimeWorkRequest>)을 통해 종속 OneTimeWorkRequest 인스턴스를 추가할 수 있습니다.

  3. WorkContinuation.enqueue() 메서드를 사용하여 WorkContinuation 체인을 큐에 추가(enqueue())할 수 있습니다.


WorkManager.getInstance(myContext)
   // Candidates to run in parallel
   .beginWith(listOf(plantName1, plantName2, plantName3))
   // Dependent work (only runs after all previous work in chain)
   .then(cache)
   .then(upload)
   // Call enqueue to kick things off
   .enqueue()

작업 체이닝에 대한 데이터 입출력 및 체이닝 시 주의해야할 점은 다음 페이지를 확인하시면 됩니다.
좀 더 깊은 내용은 다음 페이지에서 확인하시면 됩니다.


reference:
https://genius-dev.tistory.com/entry/Android-WorkManager%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%B0%B1%EA%B7%B8%EB%9D%BC%EC%9A%B4%EB%93%9C-%EC%9E%91%EC%97%85https://developer.android.com/topic/libraries/architecture/workmanager?hl=ko
https://developer.android.com/topic/libraries/architecture/workmanager?hl=ko
https://developer.android.com/topic/libraries/architecture/workmanager/how-to/define-work?hl=ko#work-constraints
https://developer.android.com/topic/libraries/architecture/workmanager/how-to/managing-work?hl=ko
https://developer.android.com/topic/libraries/architecture/workmanager/how-to/chain-work?hl=ko

profile
안드안드안드

0개의 댓글