ANDROID/Android Jetpack

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

주 녕 2022. 1. 27. 18:46
728x90

https://developer.android.com/

 

이전 포스팅에서는 간단하게 안드로이드의 백그라운드 작업에 대해 알아보았다.

이번에는 Jetpack의 WorkManager의 사용법에 대해 알아보려고 한다.

 

[Android] 백그라운드 작업

우선 WorkManager에 대해 알아보기 전에 Android의 백그라운드 처리에 대해 알아볼 것이다. 그리고 Jetpack의 WorkManager를 시작으로 다양한 백그라운드 작업 유형을 실습해보려고 한다. Android 백그라운드

junyoung-developer.tistory.com

 

WorkManager

WorkManager는 지속적인 작업에 권장되는 솔루션입니다. 앱이 다시 시작되거나 시스템이 재부팅될 때 작업이 예약된 채로 남아 있으면 그 작업은 유지됩니다. 대부분의 백그라운드 처리는 지속적인 작업을 통해 가장 잘 처리되므로 WorkManager는 백그라운드 처리에 권장하는 기본 API입니다.

 

Persistent(지속적인) 작업의 유형

👀 위의 정리해둔 이전 포스팅과 함께 보면 더욱 이해를 잘 할 수 있을 것!

다시 간단하게 설명하자면, 백그라운드 작업에는 3가지 카테고리가 있고 각 카테고리 별로 persistent/impersistent 작업이 나뉜다.

  • Immediate (즉시) : 사용자가 App과 상호작용하는 동안 작업을 완료해야 하는 경우
  • Deferrable (정시) : 바로 처리할 필요가 없거나, 특정 작업을 정확한 시간에 실행해야 하는 경우
  • Long Running (지연) : 작업을 끝내는데 시간이 좀 걸리거나, 정확한 시간에 실행할 필요가 없는 경우

 

WorkManager를 사용해야 하는 이유 (장점)

  • Work constraints (작업 제약 조건)
  • Robust scheduling (강력한 예약 관리)
  • Expedited work (신속 처리 작업)
  • Flexible retry policy (유연한 재시도 정책)
  • Work chaining (작업 체이닝)
  • Built-In threading interoperability (내장 스레딩 상호 운용성)

자세한 내용은 아래에서 더 자세하게 설명할 예정이다.

 

WorkManager 사용법

dependencies {
    val work_version = "2.7.1"
    implementation("androidx.work:work-runtime-ktx:$work_version")
}

 

WorkManager의 구성

 Worker 클래스  - task(작업)의 단위를 정의

  • doWork() 메서드 : WorkManager에서 실행할 작업을 만들기 위해선 Worker 클래스를 상속받고, 오버라이딩 (비동기)
  • 작업의 성공 여부는 doWork의 반환 값, Result를 통해 알 수 있음
    • Result.success() : 작업이 성공적으로 완료된 경우
    • Result.failure() : 작업에 실패한 경우
    • Result.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 클래스  - 언제, 어떻게 task(작업)가 실행되어야 하는지를 정의

  • Worker에 정의된 작업을 실행하기 위해서는 WorkManager 서비스로 예약해야 함
  • WorkRequest를 생성할 때, 반복 여부 & 제약 사항 등의 정보를 담음
    • OneTimeWorkRequest : 한 번만 실행할 작업을 요청하는 WorkRequest (impersistent)
    • PeriodicWorkRequest : 일정한 주기로 여러 번 작업을 요청하는 WorkRequest (persistent)
val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<UploadWorker>()
       .build()

 

 WorkManager 클래스  - WorkRequest를 스케줄링하고 실행하고 관리

  • 인스턴스를 받아와 WorkRequest를 큐에 추가하여 실행하며, WorkManager는 제한사항에 따라 최상의 상태로 작동
  • enqueue() 메서드 : WorkRequest를 WorkManager에 제출
WorkManager
    .getInstance(myContext)
    .enqueue(uploadWorkRequest)

 


 

WorkRequest에 포함될 수 있는 추가 정보

위에서 설명한 WorkManager의 장점이자 특징이 WorkRequest에 추가 정보로 포함될 수 있다.

Work constraints (작업 제약 조건) ⭐

  • 작업을 실행하는 데 최적인 조건을 선언적으로 정의함 → 최적의 조건이 충족될 때까지 작업이 지연되도록 함
  • 디바이스가 무제한 네트워크 / 유휴 상태 / 충분한 배터리인 경우에만 실행함
  • 작업 실행 중에 제약조건이 충족되지 않다면, WorkManager에서 작업을 중지하고 다시 제약조건들이 충족할 때 재시도

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)  // Wi-Fi에 연결되어 있고,
   .setRequiresCharging(true)  // 디바이스가 충전중일 때
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()
  • 제약조건과 task를 연결하기 위해서는 Constraint.Builder()를 사용하여 Constraints 인스턴스를 만들어야 함
  • 만든 Constraints 인스턴스는 WorkRequest.Builder()에 할당함

 

Expedited work (신속 처리 작업)

  • 사용자에게 중요하고 몇 분 내에 완료되는 작업은 Expedited work를 사용해야 함
  • 백그라운드에서 immediate(즉시) 실행할 작업을 예약할 수 있음
  • 신속 처리 작업의 특성
    • 중요도 : 사용자에게 중요하거나 사용자가 시작한 작업에 적합
    • 속도 : 즉시 시작되어 몇 분 안에 끝나는 짧은 작업
    • 할당량 : 포그라운드 실행 시간을 제한하는 시스템 수준 할당량에 따라 신속 처리 작업의 시작 가능 여부가 결정
    • 전원 관리 : 절전 모드, 잠자기와 같은 전력 관리 제한사항은 신속 처리 작업에 영향을 미칠 가능성이 적음
    • 지연 시간 : 시스템의 현재 워크로드로 처리 가능한 경우 시스템은 이 작업을 즉시 실행 (나중에 실행되도록 예약 불가능)

ex. 채팅 앱에서 사용자가 메세지/첨부 이미지를 전송하려는 경우, 결제/구독 흐름을 처리하는 앱

 

val request = OneTimeWorkRequestBuilder()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context)
    .enqueue(request)
  • OneTimeWorkRequest의 인스턴스를 초기화하고, 해당 인스턴스에서 setExpedited() 호출
  • enqueue()를 이용하여 해당 WorkRequest를 WorkManager에 제출

아래 내용은 잘 이해하지 못해서 더 자세한 내용은 추후 Codelab 실습과 함께 정리할 예정이다.

❇️ Android 12 보다 낮은 버전의 플랫폼에서는?

→ 신속 처리 작업의 하위 호환성을 위해 포그라운드 서비스(foreground service)를 실행할 수 있음

Worker의 getForegroundInfoAsync() / getForegroundInfo() 메서드를 사용하면 setExpeidated() 호출할 때

WorkManager가 알림을 표시할 수 있음

 

 

Robust scheduling (강력한 예약 관리) ⭐

  • flexible scheduling을 통해 one-time / repeatedly 하게 실행할 작업을 예약할 수 있음
  • 작업에 태그 / 이름을 지정하여 고유 작업 및 대체 가능한 작업을 예약하고 작업 그룹을 함께 모니터링 / 취소할 수 있음
  • 예약된 작업은 SQLite DB에 저장되고 WorkManager에서 디바이스를 재부팅해도 작업이 유지되고 예약되도록 보장
  • 절전 기능을 사용하고 권장사항(ex. Doze 모드)을 준수하기 때문에 배터리 소모를 걱정하지 않아도 됨

일회성 작업 예약

// 1번 : 간단한 작업
val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)
// 2번 : 복잡한 작업
val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .build()
  • from() : 추가 구성이 필요없는 간단한 task
  • builder : 추가 구성이 필요한 복잡한 빌더

주기적 작업 예약

ex. 주기적으로 데이터 백업, 최신 콘텐츠 다운로드

// 1시간마다 작업 예약
val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()
  • PeriodicWorkRequest를 이용한 주기적으로 실행되는 WorkRequest 객체
  • 간격 주기 == 반복 사이의 최소 시간 (정의할 수 있는 최소 반복 시간은 15분)
  • Worker가 실행될 정확한 시간은 WorkRequest의 제약 조건과 시스템의 최적화에 따라 달라짐
// 1시간마다 마지막 15분 동안 실행할 수 있는 작업
val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()
  • 작업이 실행 타이밍에 민감한 경우, 각 간격 주기 안에서 가변 기간 내에 실행되도록 구성할 수 있음
  • PeriodicWorkReques를 생성할 때 flexInterval(가변 기간)을 repeatInterval(반복 간격)과 함게 전달
  • 작업을 실행할 수 있는 기간 == (repeatInterval - flexInterval) ~ 기간의 끝
  • 이때 반복 간격(repeatInterval)은 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS (15분) 이상이어야 함
  • 이때 가변 기간(flexInterval)은 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS (5분) 이상이어야 함

❇️ PeriodicWorkRequest + 제약 조건?

→ 정의된 반복 기간보다 제약 조건이 더 우선순위!

따라서 작업 실행이 지연될 수 있고, 실행간격 내에 제약 조건이 충족되지 못하면 건너뛸 수도 있음

 

Flexible retry policy (유연한 재시도 정책) ⭐

  • 경우에 따라 실패하기도 함 → exponential backoff policy를 포함한 유연한 재시도 정책을 제공함
  • 다시 시도해야 하는 경우에는 Worker에서 Result.retry() 반환하면 retry policy, backoff policy에 의해 다시 예약됨
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()
  • backoff delay : 첫 번째 시도 후 작업을 다시 시도하기 전에 대기해야 하는 최소 시간 (MIN_BACKOFF_MILLIS 미만일 수 없음)
  • backoff policy : 다음 재시도의 backoff delay가 시간 경과에 따라 증가하는 방식 정의 (LINEAR, EXPONENTIAL 지원)
    • LINEAR - 10초, 20초, 30초... 로 재시도 간격이 늘어남
    • EXPONENTIAL - 20초, 40초, 80초... 로 시퀀스로 재시도 간격이 늘어남 (기본 정책)

 

태그

모든 WorkRequest에는 고유 식별자(unique identifier)가 있음

이 고유 식별자에 태그를 지정하여 유용하게 할 수 있음

  • 특정 태그가 있는 작업 취소 : WorkManager.cancelAllWorkByTag(String)
  • 현재 작업 진행 사항 관찰 : WorkManager.getWorkInfosByTag(String) → WorkInfo 객체 리스트 반환
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()
  • 하나의 WorkRequest에 여러 태그를 추가할 수 있음 (내부적으로 String 집합으로 저장됨)
  • WorkRequest.getTags()를 통해 태그 집합을 가져올 수 있음

 

입력 데이터 할당

작업을 실행하고자 할 때, 입력 데이터를 넣어야 해당 작업을 실행하도록 하고 싶은 경우에 사용

입력값은 Data 객체에 key-value 쌍으로 저장되며, WorkRequest에서 설정할 수 있음

// 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()
  • Worker 클래스에서는 getInputData()를 통해 입력값에 접근할 수 있음
  • 해당 코드에서는 "IMAGE_URI" key를 가진 Data 인스턴스가 없으면 Result.failure()를 반환함
  • WorkRequest에서는 setInputData()를 통해 입력값을 넣어 Data 인스턴스를 해당 Worker의 입력값으로 설정함

 

 👀 더 자세한 내용은 Codelab 실습과 함께 다음 포스팅에서 정리할 예정!


reference >

728x90