작업은 수명 동안 일련의 상태 변경을 거침.
ENQUEUED 상태에서는 제약 조건 및 초기 지연 타이밍 요구 사항이 충족되자마자 작업을 실행할 수 있음.
그 다음 RUNNING 상태로 이동한 다음 작업 결과에 따라 SUCCEEDED, FAILED로 이동하거나 결과가 재시도인 경우 ENQUEUED로 돌아갈 수 있음.
프로세스 중 어느 시점에서든 작업이 취소될 수 있으며 이때 작업은 CANCELLED 상태로 전환됨.
SUCCEEDED, FAILED 및 CANCELED는 모두 작업의 최종 상태를 나타냄. 작업이 이러한 상태에 있으면 WorkInfo.State.isFinished()는 true를 반환.
Worker와 WorkRequest를 정의한 후 마지막 단계는 작업을 대기열에 추가하는 것.
작업을 대기열에 추가하는 가장 간단한 방법은 WorkManager enqueue() 메서드를 호출하고 실행하려는 WorkRequest를 전달하는 것.
중복을 방지하기 위해 작업을 대기열에 추가할 때 주의해야함. (예: 앱은 24시간마다 백엔드 서비스에 로그를 업로드하려고 시도할 수 있으며 주의하지 않으면 동일한 작업을 여러번 대기열에 추가할 수 있게됨.)
👉 이때 unique work를 이용해 고유한 작업으로 예약할 수 있음.
val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)
Unique Work는 한번에 특정 이름을 가진 작업 인스턴스가 하나만 있음을 보장하는 개념.
ID와 달리 unique name은 사람이 읽을 수 있으며 WorkManager가 자동 생성하는 것이 아닌 개발자가 지정. 태그와 달리 고유 이름은 단일 작업 인스턴스에만 연결됨.
Unique Work는 일회성 작업과 주기적 작업 모두에 적용 가능.
반복 작업을 예약하는지 일회성 작업을 예약하는지에 따라 메서드 중 하나를 호출하여 고유한 작업 시퀀스를 만들 수 있음.
👉 두 가지 메서드는 3개의 인수를 허용
val sendLogsWorkRequest =
PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
.setConstraints(Constraints.Builder()
.setRequiresCharging(true)
.build()
)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"sendLogs", // sendLogs 작업이 대기열에 있는 동안 코드가 실행되면 기존 작업이 유지되고 새 작업이 추가되지 않음.
ExistingPeriodicWorkPolicy.KEEP,
sendLogsWorkRequest
)
긴 작업 체인을 점진적으로 구축해야 하는 경우에도 Unique Work 순서가 유용할 수 있음.
(예를 들어 사진 편집 앱을 사용하면 사용자가 일련의 작업들을 취소할 수 있고, 이것은 올바른 순서로 수행되어야 함. 이 때 앱은 실행 취소 체인을 생성하고 필요에 따라 각 실행 취소 작업을 체인에 추가할 수 있음.)
Unique work를 예약할 때 충돌이 있을 경우 취해야할 조치를 WorkManager에게 알려야 함.
작업을 대기열에 추가할 때 enum을 전달하면 됨.
일회성 작업의 경우 충돌 처리를 위한 4가지 옵션을 지원하는 ExistingWorkPolicy를 제공.
REPLACE: 기존 작업을 새 작업으로 교체. 이 옵션은 기존 작업을 취소함.
KEEP: 기존 작업을 유지하고 새 작업을 무시함.
APPEND: 기존 작업 끝에 새 작업을 추가함. 이 정책을 사용하면 새 작업이 기존 작업에 연결되어 기존 작업이 완료된 후 실행됨.
APPEND_OR_REPLACE: 전제조건이 작업 상태에 종속되지 않는다는 점을 제외하면 APPEND와 유사하게 작동. 기존 작업이 CANCELED 또는 FAILED 상태인 경우 새 작업은 계속 실행됨.
⭐ 기존 작업이 새 작업의 전제 조건이 됨. 기존 작업이 CANCELED 또는 FAILED 상태가 되면 새 작업도 CANCELED 또는 FAILED 상태가 됨.
기존 작업 상태에 관계없이 새 작업을 실행하려면 APPEND_OR_REPLACE를 사용.
주기적 작업의 경우 REPLACE 및 KEEP의 두 가지 옵션을 지원하는 ExistingPeriodicWorkPolicy를 제공함. 이러한 옵션은 ExistingWorkPolicy 옵션과 동일하게 작동함.
작업을 대기열에 넣은 후 언제든지 이름, id 또는 연결된 태그로 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>>
쿼리는 작업 ID, 태그, 현재 상태 및 Result.success(outputData)를 통해 설정된 모든 출력 데이터를 포함하는 WorkInfo 객체의 ListenableFuture를 반환.
각 메서드의 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 개체를 사용하여 대기열에 추가된 작업에 대한 복잡한 쿼리를 지원함. WorkQuery는 태그, 상태 및 고유한 작업 이름을 조합하여 작업 쿼리를 지원.
//FAILED 또는 CANCELED 상태이고 고유한 작업 이름이 "preProcess" 또는 "sync"인 "syncTag" 태그가 있는 모든 작업을 찾는 방법
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)
⭐⭐ WorkQuery의 각 구성 요소(태그, 상태 또는 이름)는 다른 구성 요소와 AND로 연결됨.
그리고 구성 요소의 각 값은 OR로 연결됨.
예: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...).
WorkQuery는 LiveData에 해당하는 getWorkInfosLiveData()와도 작동함.
대기열에 추가된 작업을 더 이상 실행할 필요가 없다면 취소를 요청할 수 있음.
작업은 이름, id 또는 연결된 태그로 취소할 수 있음.
// by id
workManager.cancelWorkById(syncWorker.id)
// by name
workManager.cancelUniqueWork("sync")
// by tag
workManager.cancelAllWorkByTag("syncTag")
내부적으로 WorkManager가 작업 상태를 확인. 이미 작업이 완료되었다면 아무일도 일어나지 않음.
작업이 완료되지 않았다면 작업상태가 CANCELLED로 변경되고 향후 작업이 실행되지 않음.
이 작업에 의존하는 모든 WorkRequest 작업도 취소됨.
RUNNING 작업은 ListenableWorker.onStopped()에 대한 호출을 받음.
📝 참고: cancelAllWorkByTag(String)는 지정된 태그와 관련된 모든 작업을 취소함.
취소를 명시적으로 요청함. (예: WorkManager.cancelWorkById(UUID) 호출)
Unique work의 경우 ExistingWorkPolicy의 REPLACE를 사용하여 새 WorkRequest를 명시적으로 대기열에 추가함. 이전 WorkRequest는 즉시 취소된 것으로 간주됨.
작업 제약 조건이 더 이상 충족되지 않음.
시스템에서 어떤 이유로든 작업을 중지하도록 앱에 지시함. 실행 기한인 10분을 초과하면 이런일이 발생할 수 있음. 작업은 추후에 다시 시도될 예정.
진행중인 모든 작업을 중단하고 Worker가 보유하고 있는 모든 리소스를 해제해야 함.
예를 들어 데이터베이스와 파일에 대해 열려 있는 핸들을 닫아야 함.
onStopped() callback
WorkManager는 Worker가 중지되자마자 ListenableWorker.onStopped()를 호출함. 보유하고 있는 모든 리소스를 닫으려면 이 메서드를 재정의.
isStopped() property
ListenableWorker.isStopped() 메서드를 호출하여 Worker가 이미 중지되었는지 확인할 수 있음. Worker에서 장기 실행 또는 반복 작업을 수행하는 경우 이 속성을 자주 확인하고 최대한 빨리 작업을 중지하기 위한 신호로 사용해야함.
참고: WorkManager에서는 onStop 신호를 수신한 Worker에서 설정한 Result를 무시함. 작업자가 이미 중지되었다고 간주하기 때문.