[Android Basics in Kotlin]WorkManager
https://developer.android.com/courses/pathways/android-basics-kotlin-unit-6-pathway-1?hl=ko
WorkManager를 사용한 작업 예약 | Android Basics in Kotlin - WorkManager - Schedule tasks | Android Developers
애플리케이션 프로세스의 현재 실행 여부와 관계없이 실행해야 하는 백그라운드 작업을 처리하는 API인 WorkManager를 사용하는 경우와 방법을 알아봅니다.
developer.android.com
이 글은 위 과정을 공부하며 작성한 글입니다.
WorkManager
다음은 기본적으로 사용되는 WorkManager 클래스이다.
- Worker: 백그라운드에서 실행하고자 하는 실제 작업의 코드를 여기에 입력한다. 이 클래스를 확장하고 doWork() 메서드를 재정의한다.
- WorkRequest: 작업 실행 요청을 나타낸다. WorkRequest를 만드는 과정에서 Worker를 전달한다. WorkRequest를 만들 때 Worker를 실행할 시점에 적용되는 Constraints 등을 지정할 수 있다.
- WorkManager: 이 클래스는 실제로 WorkRequest를 예약하고 실행한다. 지정된 제약조건을 준수하며 시스템 리소스에 부하를 분산하는 방식으로 WorkRequest를 예약한다.
즉, 실행할 일(Worker)를 WorkerRequest 클래스에 담아 WorkManager가 예약하고 실행한다.
1. Worker 클래스 만들기
Worker를 상속하는 BlurWorker 클래스를 만든다.
doWork() 메서드를 재정의해 Result 값으로 Result.success() 또는 Result.failure()를 반환한다.
class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
override fun doWork(): Result {
}
}
2. ViewModel에서 WorkManager 가져오기
ViewModel에서 WorkManager 인스터슨의 클래스 변수를 만든다.
private val workManager = WorkManager.getInstance(application)
3. WorkManager에서 WorkRequest를 큐에 추가
WorkRequest에는 두 가지 유형이 있다.
- OneTimeWorkRequest: 한 번만 실행하는 WorkRequest
- PeriodicWorkRequest: 일정 주기로 반복하는 WorkRequest
WorkerManager 인스턴스를 사용해 WorkRequest를 큐에 추가한다.
internal fun applyBlur(blurLevel: Int) {
workManager.enqueue(OneTimeWorkRequest.from(BlurWorker::class.java))
}
입력 및 출력
입력 및 출력은 Data 객체를 통해 안팎으로 전달된다. Data 객체는 키-값 쌍의 경량 컨테이너이다.
WorkRequest의 안팎으로 전달될 수 있는 소량의 데이터를 저장하기 위한 것이다.
- 데이터 입력 객체
Data.Builder 객체와 putString()을 이용해 데이터 객체에 데이터를 추가한다.
val builder = Data.Builder()
imageUri?.let {
builder.putString(KEY_IMAGE_URI, imageUri.toString())
}
return builder.build() // Data 객체 build 해 리턴
- WorkRequest에 데이터 객체 전달
WorkRequestBuilder와 setInputData()를 통해 WorkRequest객체에 Data 객체를 전달한다.
val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
.setInputData(createInputDataForUri())
.build()
workManager.enqueue(blurRequest)
- Worker 클래스에서 데이터 객체 사용
Data 객체에서 데이터를 받아 사용한다.
override fun doWork: Result{
val resourceUri = inputData.getString(KEY_IMAGE_URI)
}
데이터 입력 및 사용 과정을 정리하면 다음과 같다.
Data -> WorkRequest -> Worker 내 inputData로 사용 |
- 데이터 출력
데이터 출력은 workDataOf() 메소드에 (KEY to value) 형태의 인수를 넣어 데이터 객체를 생성한다.
데이터 객체를 Result.success() 메소드를 사용해 WorkManager에 반환한다.
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
return Result.success(outputData)
작업 체이닝
WorkManager를 사용하면 순서대로 실행되거나 동시에 실행되는 별도의 WorkerRequest를 만들 수 있다.
체이닝을 위한 또 다른 기능은 WorkRequest의 출력이 체인 내 다음 WorkRequest의 입력이 된다는 점이다.
다음 그림은 현재 예제에서 사용하는 작업 체인이다.
지금까지는 이미지 블러처리를 하는 Worker만 있었지만, 임시파일을 정리하는 Worker와 이미지를 영구적으로 저장하는 Worker를 생성해 작업 체인을 구현한다.
Worker를 하나만 실행하는 것이 아니라 WorkRequest 체인을 실행하기 위해선 beginWith()을 사용한다.
Worker 작업을 실행 및 예약하던 코드인 enqueue 대신 beginWith()을 호출한다.
그러면 WorkRequest 체인을 정의하는 WorkContinuation이 반환된다.
then() 메서드를 호출해 작업을 이 WorkRequest 체인에 추가하고 마지막에 enqueue() 메서드를 통해 실행한다.
val continuation = workManager.beginWith(workA)
continuation.then(workB) // FYI, then() returns a new WorkContinuation instance
.then(workC)
.enqueue() // Enqueues the WorkContinuation which is a chain of work
고유 작업 체인
데이터 동기화처럼 작업 체인을 한 번에 하나씩만 실행해야 하는 경우가 있다.
이렇게 하려면 beginWith() 대신 beginUniqueWork()를 사용하고 고유한 String 이름을 제공하면 된다.
또한 이미 작업하고 있을 경우 어떤 식으로 처리할 것인지에 대한 ExistingWorkPolicy도 함께 전달해야 한다.
사용할 수 있는 옵션은 REPLACE, KEEP, APPEND이다. 사용 예시는 다음과 같다.
var continuation = workManager.beginUniqueWork(
KEY,
ExistingWorkPolicy.REPLACE,
OnetimeWorkerRequest.from(CleanupWorker::class.java)
)
WorkInfo
WorkInfo는 WorkRequest의 현재 상태에 관한 다음과 같은 세부정보가 포함된 객체이다.
- 작업 상태: BLOCKED, CANCELLED, ENQUEUED, FAILED, RUNNING, SUCCEEDED
- WorkRequest가 완료된 경우 작업의 모든 출력 데이터
다음 표는 LiveData<WorkInfo> 나 LiveData<List<WorkInfo>> 객체를 가져오는 세 가지 방법이다.
각 상황에 맞는 방법을 선택하면 되는 데, 이번 예시에서는 save작업에서 getWorkInfosByTag를 사용한다.
사용자가 여러 이미지를 블러 처리하는 경우 이미지 저장 WorkRequest의 태그는 같지만 ID는 같지 않기 때문이다.
또한 고유 작업 외의 작업에서도 반환값(WorkInfo)를 받아야기 때문에 고유 체인 이름을 사용하는 방식도 적절치 않다.
1. 작업 태그 지정
WorkRequest 빌드 시 addTag() 메서드를 통해 태그를 추가해준다.
val save = OneTimeWorkRequestBuilder<SaveWorker>()
.addTag(TAG_OUTPUT)
.build()
2. WorkInfo 가져오기
태그 통해 WorkInfo를 가져온다.
internal val outputWorkInfos: LiveData<List<WorkInfo>>
outputWorkInfos = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
3. WorkInfo 내 Data 객체 가져오기
각 WorkInfo에는 Data 객체를 가져올 수 있는 getOutputData 메서드가 있다.
코틀린에서 자동으로 생성하는 변수인 outputData를 통해 이 메서드에 액세스할 수 있다.
val outputUri = workInfo.outputData.getString(KEY)
작업 취소
WorkManager를 사용하면 ID, 태그, 고유 체인 이름을 통해 작업을 취소할 수 있다.
여기서는 특정 단계뿐 아니라 체인의 모든 작업을 취소할 것이기 때문에 고유한 체인 이름으로 작업을 취소하는 것이 좋다.
이름으로 작업을 취소할 때는 cancelUniqueWork 내 태그를 전달해 작업을 취소한다.
workManager.cancelUniqueWork(KEY)
작업 제약 조건
WorkManager는 Constraints(제약조건)을 지원한다.
1. 제약 조건 만들기 및 추가
Constraints 객체를 만들려면 Constratins.Builder를 사용한다.
그런 다음 원하는 제약 조건을 설정하고, setRequireCharging(충전 제약 조건) 메서드를 사용해 WorkRequest에 추가한다.
val constraints = Constraints.Builder()
.setRequiresCharging(true)
.build()
workRequest 객체
.setConstraints(constraints)
.build()