작업 관리

WorkerWorkRequest를 정의했다면 작업을 큐에 추가하는 마지막 단계가 남아 있습니다. 작업을 큐에 추가하는 가장 간단한 방법은 WorkManager enqueue() 메서드를 호출하여 실행하려는 WorkRequest를 전달하는 것입니다.

Kotlin


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

Java


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

중복 작업을 방지하려면 작업을 큐에 추가할 때 주의하시기 바랍니다. 예를 들어 앱에서 24시간마다 백엔드 서비스에 로그를 업로드하려고 시도할 수 있습니다. 한 번만 실행해야 하는 작업임에도 동일한 작업이 여러 번 큐에 추가될 수 있으므로 주의해야 합니다. 한 번만 큐에 추가하려면 작업을 고유 작업으로 예약하면 됩니다.

고유 작업

고유 작업은 특정 이름의 작업 인스턴스가 한 번에 하나만 있도록 보장하는 강력한 개념입니다. ID와는 달리 고유 이름은 사람이 읽을 수 있으며 WorkManager에서 자동으로 생성하는 대신 개발자가 지정합니다. 고유 이름은 태그와 달리 단일 작업 인스턴스에만 연결됩니다.

고유 작업은 일회성 작업과 주기적 작업에 모두 적용할 수 있습니다. 예약하는 작업이 반복 작업인지, 일회성 작업인지에 따라 다음 메서드 중 하나를 호출하여 고유 작업 시퀀스를 만드세요.

두 메서드 모두 세 가지 인수를 허용합니다.

  • uniqueWorkName - 작업 요청을 고유하게 식별하는 데 사용되는 String입니다.
  • existingWorkPolicy - 고유 이름이 있는 작업 체인이 아직 완료되지 않은 경우 WorkManager에 해야 할 작업을 알려주는 enum입니다. 자세한 내용은 충돌 해결 정책을 참고하세요.
  • work - 예약할 WorkRequest입니다.

고유 작업을 사용하면 앞에서 언급한 중복 예약 문제를 해결할 수 있습니다.

Kotlin


val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build()
            )
           .build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)

Java


PeriodicWorkRequest sendLogsWorkRequest = new
      PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
              .setConstraints(new Constraints.Builder()
              .setRequiresCharging(true)
          .build()
      )
     .build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
     "sendLogs",
     ExistingPeriodicWorkPolicy.KEEP,
     sendLogsWorkRequest);

이제 sendLogs 작업이 이미 큐에 있는 상태에서 코드가 실행되면 기존 작업은 그대로 유지되고 새로운 작업이 추가되지 않습니다.

고유 작업 시퀀스는 긴 작업 체인을 점진적으로 구축해야 하는 경우에도 유용할 수 있습니다. 사용자가 긴 체인으로 연결된 작업을 실행취소할 수 있는 사진 편집 앱을 예로 들어 보겠습니다. 각 실행취소 작업에 시간이 어느 정도 걸릴 수는 있지만 올바른 순서로 실행취소해야 합니다. 이런 경우 앱에서 'undo' 체인을 만들고 필요에 따라 체인에 각 실행취소 작업을 추가할 수 있습니다. 자세한 내용은 작업 체이닝을 참고하세요.

충돌 해결 정책

고유 작업을 예약할 때는 충돌 발생 시 어떤 작업을 해야 하는지 WorkManager에 알려야 합니다. 작업을 큐에 추가할 때 enum을 전달하여 알릴 수 있습니다.

일회성 작업의 경우 충돌을 처리하는 4가지 옵션을 지원하는 ExistingWorkPolicy를 제공합니다.

  • REPLACE: 기존 작업을 새 작업으로 대체합니다. 기존 작업을 취소하는 옵션입니다.
  • KEEP: 기존 작업을 유지하고 새 작업을 무시합니다.
  • APPEND: 새 작업을 기존 작업의 끝에 추가합니다. 새 작업을 기존 작업에 체이닝하여 기존 작업이 완료된 후에 새 작업을 실행하는 정책입니다.

기존 작업은 새 작업의 전제 조건이 됩니다. 기존 작업이 CANCELLED 또는 FAILED가 되면 새 작업도 CANCELLED 또는 FAILED가 됩니다. 기존 작업의 상태와 관계없이 새 작업을 실행하려면 대신 APPEND_OR_REPLACE를 사용하세요.

  • APPEND_OR_REPLACEAPPEND와 유사하게 작동하지만 전제 조건 작업 상태에 의존하지 않는 것은 다릅니다. 기존 작업이 CANCELLED 또는 FAILED라도 새 작업은 계속 실행됩니다.

주기적 작업의 경우 개발자가 제공하는 ExistingPeriodicWorkPolicy는 두 가지 옵션(REPLACE, KEEP)을 지원합니다. 두 옵션은 ExistingWorkPolicy 옵션과 동일하게 작동합니다.

작업 관찰

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

Kotlin


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

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

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

Java


// 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, 태그, 현재 State, Result.success(outputData)를 통해 설정된 출력 데이터가 포함됩니다.

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

Kotlin


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


Java


workManager.getWorkInfoByIdLiveData(syncWorker.id)
        .observe(getViewLifecycleOwner(), workInfo -> {
    if (workInfo.getState() != null &&
            workInfo.getState() == WorkInfo.State.SUCCEEDED) {
        Snackbar.make(requireView(),
                    R.string.work_completed, Snackbar.LENGTH_SHORT)
                .show();
   }
});

복잡한 작업 쿼리

WorkManager 2.4.0 이상에서는 WorkQuery 객체를 사용하여 큐에 추가된 작업의 복잡한 쿼리를 지원합니다. WorkQuery는 태그, 상태, 고유 작업 이름을 조합하여 작업 쿼리를 지원합니다.

다음 예에서는 태그가 'syncTag'이고 FAILED 또는 CANCELLED 상태이며 고유 작업 이름이 'preProcess' 또는 'sync'인 작업을 모두 찾을 방법을 보여줍니다.

Kotlin


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)

Java


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

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

WorkQuery의 각 구성요소(태그, 상태, 이름)는 다른 구성요소와 AND로 연결됩니다. 구성요소의 각 값은 OR로 연결됩니다. 예: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)

WorkQuery는 상응하는 LiveData 코드인 getWorkInfosLiveData()와도 호환됩니다.

작업 취소 및 중지

이전에 큐에 추가한 작업을 더 이상 실행하지 않으려면 작업을 취소하도록 요청할 수 있습니다. name, id 또는 연결된 tag로 작업을 취소하세요.

Kotlin


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

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

// by tag
workManager.cancelAllWorkByTag("syncTag")


Java


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

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

// by tag
workManager.cancelAllWorkByTag("syncTag");

내부적으로 WorkManager에서는 작업의 State를 확인합니다. 작업이 이미 완료되었다면 아무런 변화가 없습니다. 완료되지 않은 경우 작업 상태가 CANCELLED로 변경되고 향후에 작업이 실행되지 않습니다. 작업에 종속된 모든 WorkRequest 작업도 CANCELLED로 상태가 변경됩니다.

현재 RUNNING 작업은 ListenableWorker.onStopped() 호출을 수신합니다. 이 메서드를 재정의하여 잠재적으로 필요한 정리 작업을 처리할 수 있습니다. 자세한 내용은 실행 중인 작업자 중지를 참고하세요.

실행 중인 작업자 중지

실행 중인 Worker를 WorkManager에서 중지할 수 있는 몇 가지 이유는 다음과 같습니다.

  • 개발자가 명시적으로 작업자 취소를 요청한 경우(예: WorkManager.cancelWorkById(UUID) 호출)
  • 고유 작업에서 ExistingWorkPolicyREPLACE로 설정한 상태로 새 WorkRequest를 명시적으로 큐에 추가한 경우. 이전 WorkRequest는 즉시 취소되었다고 간주됩니다.
  • 작업의 제약조건이 더 이상 충족되지 않는 경우
  • 시스템에서 어떤 이유로 작업을 중지하라고 앱에 지시한 경우. 실행 기한이 10분을 초과하는 상황에서 발생할 수 있습니다. 작업이 나중에 다시 시도되도록 예약됩니다.

이러한 조건에서 작업자는 중지됩니다.

진행 중인 모든 작업을 취소하는 데 협조하고 작업자가 보유한 모든 리소스를 해제해야 합니다. 예를 들어 데이터베이스와 파일에서 사용 중인 핸들은 이 시점에서 종료해야 합니다. 개발자가 원할 때 작업자의 중지 시점을 파악할 수 있는 메커니즘은 두 가지입니다.

onStopped() 콜백

WorkManager에서는 작업자가 중지되자마자 ListenableWorker.onStopped()를 호출합니다. 이 메서드를 재정의하여 보유하고 있을 수 있는 리소스를 모두 닫습니다.

isStopped() 속성

ListenableWorker.isStopped() 메서드를 호출하여 작업자가 이미 중지되었는지 확인할 수 있습니다. 작업자에서 장기 실행 작업이나 반복 작업을 실행한다면 이 속성을 자주 확인하여 최대한 빨리 작업을 중지하는 신호로 사용해야 합니다.

참고: WorkManager에서는 onStop 신호를 수신한 작업자에서 설정한 Result를 무시합니다. 작업자가 이미 중지되었다고 간주하기 때문입니다.