오늘도 더 나은 코드를 작성하였습니까?

작업 상태 확인후 관리하고 취소까지 본문

Android Jetpack Architecture/Workmanager

작업 상태 확인후 관리하고 취소까지

hik14 2021. 3. 12. 15:36

Work는 전체 기간에 걸쳐 상태를 변경 가진다.

 

one-time Request

ENQUEUED - 일회성 작업이 시작하는 상태 (제약사항 및 지연 타이밍이 맞으면 실행)

RUNNING 상태로 이동

 

최종단계

SUCCEEDED

FAILED

retry 라면 다시 ENQUEUED로 다시 이동할 수 있습니다.

 

프로세스의 어떤 시점에서든 작업을 취소할 수 있고 작업은 취소 시점에서 CANCELLED 상태로 이동한다.

 

작업의 최종단계들 에선 WorkInfo.State.isFinished()가 true를 반환한다.

 

Periodic Request

SUCCEEDEDFAILED 상태는 one-time 및 chaining 에만 존재한다.

실행할 때마다 결과와 관계없이 작업이 다시 예약된다.

작업관리

Worker WorkRequest를 정의했다면 작업을 큐에 추가하여 실행한다.

WorkManager enqueue() 메서드를 호출하여 실행하려는 WorkRequest를 넣어준다.

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

중복 작업을 방지하려면 작업을 큐에 추가할 때 주의해야 된다.

예를 들어 앱에서 24시간마다 백엔드 서비스에 로그를 업로드하려고 시도할 수 있습니다.

주의하지 않으면 한 번만 실행해야 하는 작업임에도 동일한 작업이 여러 번 큐에 추가되기도 합니다. 

 

UniqueWork

uniqueWork은 uniqueName의 작업 인스턴스가 한 번에 하나만 있도록 보장하는 강력한 개념입니다.

ID와는 달리 고유 이름은 사람이 읽을 수 있으며 WorkManager에서 자동으로 생성하는 대신 개발자가 지정합니다.

 

uniqueName 태그와 달리 a single instance of work에만 연결됩니다.

즉, Tag는 논리적으로 동일한 작업 및 단계에 수행되는 work인스턴스에 모두 동일한 Tag를 붙일 수 있다.

하지만 uniqueName은 work 인스턴스 오직 1개에 하게 붙힐 수 있다.

 

예)   이미지 2개에 동시에 어떤 작업을 처리한다고 가정할때

 

이미지1 ---->  work1(tag: download) -- work2(tag: process) -- work3(tag: upLoad)

이미지2 ----> work1(tag: download uniqueName: image2 down) -- work2(tag: process) -- work3(tag: upLoad)

      

 - WorkManager.enqueueUniqueWork() (일회성 작업)

 - WorkManager.enqueueUniquePeriodicWork()(주기적 작업)

 

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

  • uniqueWorkName - 작업 요청을 고유하게 식별하는 데 사용되는 String
  • existingWorkPolicy - uniqueWorkname이 있는 작업 체인이 아직 완료되지 않은 경우 WorkManager에 해야 할 작업을 알려줌
  • work - 예약할 WorkRequest

예시 코드

val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS) // 24시간 마다 실행
           .setConstraints(Constraints.Builder() 
               .setRequiresCharging(true) // 배터리가 충전중일때 실행
               .build()
            )
           .build()
           
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs", // 고유이름
           ExistingPeriodicWorkPolicy.KEEP, // 작업매니저 큐에 이미 동일한 작업이 있다면 유지(추가x)
           sendLogsWorkRequest // 작업요청. 
)

 

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

 

uniqueWork 시퀀스는 긴 작업 체인을 점진적으로 구축해야 하는 경우에 도 많이 사용된다.

 

사용자가 긴 체인으로 연결된 작업을 실행취소할 수 있는 사진 편집 앱이 있다 하자

작업에 실행을 취소한다면, 시간이 어느 정도 걸릴 수는 있지만 해당 작업체인에 대해서 올바른 순서로 실행취소해야 합니다.

이런 경우 앱에서 'undo' 체인을 만들고 필요에 따라 체인에 각 실행취소 작업을 추가할 수 있습니다.

 

충돌해결 정책

one-time work

  • REPLACE:  기존작업을 새 작업으로 대체합니다. 기존 작업을 취소된다.
  • KEEP:  기존 작업을 유지하고 새 작업을 무시합니다.
  • APPEND: 새 작업을 기존 작업의 끝에 추가합니다. 새 작업을 기존 작업에 체이닝하여 기존 작업이 완료된 후에 새 작업을 실행
  • APPEND_OR_REPLACE:  기존 작업의 상태와 상관없이 새작업을 실행한다.

일반적으로 기존 작업이 CANCELLED 또는 FAILED가 되면 새 작업도 CANCELLED 또는 FAILED가 됩니다.

기존 작업의 상태와 관계없이 새 작업을 실행하려면 대신 APPEND_OR_REPLACE를 사용

 

Periodic Request

 

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를 반환하고

WorkInfo에는 작업의 id, 태그, 현재 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()
   }
}

 

복잡한 쿼리 (구체적인 식별자(태그,id)뿐 아니라 상태, uniqueName 까지 반영하여 특정 작업을 관찰가능하다)

 

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

WorkQuery는 태그, 상태, 고유 작업 이름을 조합하여 작업 쿼리를 생성한다.

 

각각 요소는 AND로 연결되어 반영되고 한 요소 안에서의 파라미터는 OR로 반영된다. 

Tag ( OR )

State ( OR )

UniqueWorkname ( OR )

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는 상응하는 LiveData 코드인 getWorkInfosLiveData()와도 호환됩니다.

 

실행중인 작업 취소 및 중지하기

 

실행 중인 Worker를 WorkManager에서 중지할 수 있는 몇 가지 이유.

 

- 개발자가 명시적으로 작업자 취소를 요청한 경우(예: WorkManager.cancelWorkById(UUID) 호출)

 

- UniqueWork에서 ExistingWorkPolicy를 REPLACE로 설정한 상태로 새 WorkRequest를 명시적으로 큐에 추가한 경우

   WorkManager는 이전 WorkRequest는 즉시 취소한다

 

- 작업의 제약조건이 더 이상 충족되지 않는 경우

 

- Andorid OS에서 특정 이유로 작업을 중지하라고 App에 지시한 경우.

  실행 기한이 10분을 초과하는 상황에서 발생할 수 있습니다. 작업이 나중에 다시 시도되도록 예약됩니다.

 

진행 중인 모든 작업을 취소하고 Worker가 보유한 모든 자원을 해제해야 한다. 

예를 들어 데이터베이스와 파일에서 사용 중인 경우 이 시점에 처리해야된다. 

 

 

개발자가 원할 때 작업자의 중지 시점을 파악할 수 있는 방법은 두 가지입니다

 

onStopped() 콜백

WorkManager에서는 Worker가 중지되자마자 ListenableWorker.onStopped()를 호출합니다.

이 메서드를 재정의하여 보유하고 있을 수 있는 리소스(파일 데이터베이스 기타 등등)를 모두 닫습니다.

 

isStopped() 속성

ListenableWorker.isStopped() 메서드를 호출하여 Worker가 이미 중지되었는지 확인할 수 있습니다.

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

 

* WorkManager에서는 onStop 신호를 수신한 작업자에서 설정한 Result를 무시해버린다.( 없는 것으로 간주한다.)

작업자가 이미 중지되었다고 간주하기 때문입니다.

 

 

'Android Jetpack Architecture > Workmanager' 카테고리의 다른 글

WorkChaining  (0) 2021.03.12
Worker 중간 진행률 및 관찰  (0) 2021.03.12
WorkRequest 만들기.  (0) 2021.03.12
Workmanager codelab  (0) 2021.03.12
Workmanager 기본 사용법  (0) 2021.03.11