ANDROID/Android Jetpack

[Android/Jetpack] AAC - WorkManager (2)

주 녕 2022. 1. 31. 16:07
728x90

https://developer.android.com/

이 포스팅 보는 모든 분들 새해 복 많이 받으세요😊

 

 

그럼 지난 포스팅에 이어 WorkManager의 작업 상태, 관리, 관찰 그리고 마지막으로 체이닝까지 공부해보려고 한다! 

이해를 돕기 위해서는 지난 포스팅과 함께 보는 것이 좋을 것 같다👀

 

[Android/Jetpack] AAC - WorkManager (Persistent Background)

이전 포스팅에서는 간단하게 안드로이드의 백그라운드 작업에 대해 알아보았다. 이번에는 Jetpack의 WorkManager의 사용법에 대해 알아보려고 한다. [Android] 백그라운드 작업 우선 WorkManager에 대해 알

junyoung-developer.tistory.com

 

작업 상태

작업(task or work)은 전체 기간 동안 일련의 State 변경을 거침

일회성 작업 상태 (One-time work states)

공식 문서 : 일회성 작업 상태 다이어그램

  1. ENQUEUED 상태로 시작
    • 제약 조건/초기 지연 타이밍 요구사항이 충족되는 즉시 실행
  2. RUNNING 상태로 이동 (실행)
  3. 작업 결과에 따라 SUCCEEDED, FAILED
    • 결과가 retry라면 다시 ENQUEUED 상태로 이동 가능
    • 언제든지 작업이 취소되었다면 CANCELLED 상태로 이동

*SUCCEEDED, FAILED, CANCELLED 상태 : 작업의 최종 상태 (WorkInfo.State.isFinished()가 true)

 

주기적 작업 상태 (Periodic work states)

공식 문서 : 주기적 작업 상태 다이어그램

  1. ENQUEUED 상태로 시작
  2. RUNNING 상태로 이동 (실행)
    1. 결과에 상관없이 다시 ENQUEUED 상태로 이동
    2. 언제든지 작업이 취소되었다면 CANCELLED 상태로 이동

*해당 작업은 종료되지 않으므로 SUCCEEDED, FAILED 상태가 없음

 

차단된 상태 (Blocked state)

이 부분은 작업 체이닝에서 다룰 예정이다.

 


 

작업 관리

이전 포스팅에서는 정의된 Worker와 WorkRequest를 WorkManager의 enquene() 메서드를 이용하여 큐에 추가하였다.

하지만 큐에 추가하면서 작업을 중복하여 추가할 수도 있고, 작업을 예약 중에 충돌할 수도 있기 때문에 작업을 관리해주어야 한다.

 

고유 작업 (Unique Work)

중복 작업을 막기 위한 방법으로 특정 '이름(name)'의 작업 인스턴스가 한 번에 하나만 있도록 보장하는 개념

Work의 이름(name) ≠ 태그(tag)
  • 태그는 WorkRequest의 고유 식별자(WorkManager에서 자동생성)에 붙이는 이름 (유용한 기능)
  • 이름은 자동 생성되는 것이 아닌 개발자가 지정하는 것으로 작업 인스턴스에만 연결됨

고유 작업은 일회성 / 주기적 작업에 모두 적용할 수 있음

  • WorkManager.equeueUniqueWork()
  • WorkManager.enqueueUniquePeriodicWork()
    • uniqueWorkName : WorkRequest를 고유하게 식별하는데 사용할 String(이름)
    • existingWorkPolicy : 충돌 시 WorkManager에서 해야 할 작업을 알려주는 enum 
    • work : 예약할 workRequest
val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build())
           .build()
           
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)
  • sendLogsWorkRequest는 24시간 마다 실행되는 주기적 작업으로, 충전 중일 때만 실행되는 작업
  • sendLogsWorkRequest는 "sendLog"라는 이름의 고유 작업으로 WorkManager에 예약됨
  • 만약 해당 작업이 충돌한다면 기존 작업을 유지하고, 새로운 작업을 무시함

 

충돌 해결 정책 (ExistingWorkPolicy, 고유 작업의 2번째 매개변수)

이미 큐에 같은 이름의 작업이 있다면, 충돌이 발생한 것이다.

따라서 이 충돌이 발생하면 WorkManager에게 어떤 작업을 해야하는지 enum을 통해 알려주어야 한다.

  • REPLACE : 기존 작업을 취소하고, 새 작업으로 대체
  • KEEP : 기존 작업을 유지하고, 새 작업을 무시
  • APPEND : 새 작업을 기존 작업 뒤에 추가 (새 작업을 기존 작업에 체이닝 → 기존 작업이 CANCELED / FAILED이면 새 작업도)
  • APPEND_OR_REPLACE : APPEND와 유사하지만, 기존 작업이 CANCELED / FAILED 라도 새 작업을 실행

주기적인 작업은 REPLACEKEEP을 지원함

 

작업 관찰

작업을 큐에 추가하면, 이름, id, tag로 WorkManager에 쿼리하여 작업의 상태를 확인할 수 있다.

또한 WorkManager 2.4.0 이상에서는 WorkQuery 객체를 통해 큐에 추가된 작업의 복잡한 쿼리를 지원한다.

따라서 WorkQuery에 tag, State, 이름을 조합하여 작업을 찾을 수 있다.

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

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

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

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)
  • 해당 결과는 WorkInfo 객체의 ListenableFuture를 통해 반환
  • 이 반환 객체에는 작업의 id, tag, 현재 State, Result.success(outputData)를 통해 설정한 출력 데이터가 포함됨
  • WorkQuery의 예는 태그가 "syncTag"이며, FAILED/CANCELLED 상태이며, 이름이 "preProcess" or "sync"인 작업
    • WorkQuery는 각 '태그, 상태, 이름'(구성요소)은 AND로 연결되며, 구성요소 내부의 각 값은 OR로 연결됨

각 메서드의 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()
   }
}

 

작업 취소 및 중지

큐에 추가한 작업을 더 이상 실행하지 않으려면 작업을 취소하도록 요청하면 된다.

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

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

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

내부적으로 WorkManager에서는 작업의 State를 확인한다.

  • 이미 작업이 완료(SUCCEEDED, FAILED, CANCELLED)되었다면 아무런 변화가 없을 것
  • 완료되지 않은 경우, CANCELLED로 변경되며 작업이 취소됨 (해당 작업에 체이닝된 모든 WorkRequest도 취소됨)
  • 현재 RUNNING 작업은 ListenableWorker.onStopped() 호출을 받음

 

실행 중인 Worker 중지

  • 개발자가 명시적으로 Worker 취소를 요청한 경우 (위의 경우)
  • 고유 작업에서 ExistingWorkPolicy를 REPLACE로 설정한 상태로 새 WorkRequest를 명시적으로 큐에 추가한 경우
  • 작업의 제약조건이 더 이상 충족되지 않는 경우
  • 시스템에서 어떤 이유로 작업을 중지하라고 앱에 지시한 경우 (ex. 실행 시간을 10분을 초과하는 상황에서 발생 → 다시 예약됨)

이러한 조건에서 Worker는 중지되며, 진행 중인 모든 작업을 취소하고 Worker가 보유한 리소스를 해제해야 함

 

onStopped() 콜백

Worker가 중지되지마자 ListenableWorker.onStopped()를 호출함

이 메서드를 재정의하여 보유하고 있는 리소스를 모두 닫으면 됨

 

isStopped() 속성

이 메서드를 통하여 Worker가 이미 중지되었는지를 확인할 수 있음

Worker에서 장기 실행 작업 / 반복 작업을 실행한다면 이 속성을 자주 확인하여 최대한 빨리 작업을 중지하는 신호로 사용해야 함

 


 

작업 체이닝

복잡한 작업의 경우 직관적인 인터페이스를 사용하여 개별 작업을 함께 체이닝하면

순차적으로 실행할 작업과 동시에 실행할 작업을 제어할 수 있음

 

Built-In threading interoperability (내장 스레딩 상호 운용성)

  • 코루틴, RxJava와 원활하게 통합
  • 자체 비동기 API를 연결할 수 있는 유연성 제공

 


reference >

  • https://developer.android.com/topic/libraries/architecture/workmanager/how-to/states-and-observation?hl=ko
  • 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
728x90