
학습할 내용
프로젝트에 WorkManager 추가
단순한 작업 예약
입력 및 출력 매개변수
작업 체이닝
고유 작업
UI에 작업 상태 표시
작업 취소
작업 제약 조건
이미지를 블러 처리하여 결과를 파일에 저장하는 앱 Blur-O-Matic을 만들어보자.
스타터 앱 클론
git clone -b start_kotlin https://github.com/googlecodelabs/android-workmanager
gradle
WorkManager를 사용하려면 Gradle에 추가해야한다. 여기에서 최신 버전을 확인한다.
implementation "androidx.work:work-runtime-ktx:2.7.1"
Worker
백그라운드에서 실행하고자 하는 실제 작업의 코드를 이곳에 작성한다.
WorkRequest
작업 실행을 위한 요청을 나타낸다. WorkRequest를 만드는 과정에서 Worker를 전달(pass) 한다. WorkRequest를 만들 때 Worker를 실행할 시점에 적용되는 Constraints 등을 지정할 수 있다.
WorkManager
실제로 WorkRequest를 예약하고 실행한다. 지정된 제약 조건을 준수하면서 시스템 리소스에 부하를 분산하는 방식으로 WorkRequest를 예약한다.
BlurWorker 클래스를 새로 생성한다.
class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
}
BlurWorker 클래스안에 doWork()를 재정의한다.
override fun doWork(): Result{
val appContext = applicationContext
makeStatusNotification("Blurring Image", appContext)
return try{
val picture = BitmapFactory.decodeResource(
appContext.resources, R.drawable.test
)
val output = blurBitmap(picture, appContext)
val outputUri = writeBitmapToFile(appContext, output)
makeStatusNotification("Output is $outputUri", appContext)
Result.success()
} catch (throwable: Throwable){
// Timber.e(throwable, "Error applying blur")
Result.failure()
}
}
Bitmap을 만들어 picture에 담는다.WorkerUtils에서 blurBitmap 메서드를 호출하여 블러 처리된 비트맵을 가져와 output에 담는다.WorkerUtils에서 writeBitmapToFile 메서드를 호출하여 이 비트맵을 임시 파일에 쓴다. 반환된 URI를 outputUri 변수에 담는다.makeStatusNotification 메서드를 호출하여 URI를 표시하는 알림을 만든다.Result.success()를 반환BlurViewModel.kt에 WorkManager를 가져와 workManger 변수에 담는다.
private val workManager = WorkManager.getInstance(application)
WorkRequest를 만들어 WorkManager에게 실행하도록 지시한다.
WorkRequest 유형은 2가지가 있다.
OneTimeWorkRequest: 한 번만 실행PeriodicWorkRequest: 일정 주기로 반복 실행적용 버튼을 클릭했을 때 한 번만 블러 처리를 적용하도록 하려면 OneTimeWorkRequest를 사요할 수 있다.
BlurViewModel.kt에 applyBlur() 메서드를 추가한다.
Internal fun applyBlur(blurLevel: Int) {
workManager.enqueue(OneTimeWorkRequest.from(BlurWorker::class.java))
}
사용자가 직접 이미지를 블러 처리하려면 선택한 이미지의 URI를 WorkRequest의 입력으로 제공해야한다.
입력과 출력은 Data 객체를 통해 안팎으로 전달된다.
Data 객체는 키-값 쌍의 경량 컨테이너로 WorkRequest의 안팎으로 소량의 데이터를 저장하는데 사용한다.
private fun createInputDataForUri(): Data {
val builder = Data.Builder()
imageUri?.let {
builder.putString(KEY_IMAGE_URI, imageUri.toString())
}
return builder.build()
}
Data.Builder object를 만든다.imageUri가 null이 아닌 URI이면 putString()을 사용하여 Data 객체에 추가한다. 키와 값을 사용한다.build()를 호출해 Data 객체를 만들어 반환한다.applyBlure() 메서드에 다음 코드를 추가한다.
val blurRequest = OneTimeWorkRequestBuilder<BlurWorker>()
.setInputData(createInputDataForUri())
.build()
OneTimeWorkRequest.Builder를 만든다.createInputDataForUri의 결과를 전달하고, build()한다.workManager.enqueue(blurRequest)를 통해 요청을 큐에 추가한다.
전달한 URI를 Data 객체에서 가져오도록 BlurWorker의 doWork() 메서드를 수정한다.
단일 작업을 처리하는 방법을 배웠다. WorkManager를 사용하면 순서대로 실행되거나 동시에 실행되는 별도의 WorkerRequest를 만들 수 있다. 이 과정에서는 작업 체인을 사용한다.
기존 Worker는 블러처리를 담당하는 Worker이다. 임시 파일을 정리하는 Worker와 이미지를 영구적으로 저장하는 Worker를 만들어보자.
먼저 worker 패키지에 Worker를 확장하는 클래스 CleanupWorker와 SaveImageToFileWorker를 생성한다.
BlurWorker와 유사하게 클래스를 생성하고, doWork()를 재정의한다.
CleanupWorker는 입력을 받거나 출력을 내보내는 기능이 없기 때문에 임시 파일이 있으면 항상 삭제한다.
SaveImageToFileWorker도 마찬가지로 파일을 생성하고, doWork()를 재정의한다.
WorkRequest 체인을 실행하도록 BlurViewModel의 applyBlur 메서드를 수정한다.
이전에 작성한 blurRequest와 workManager.enqueue()를 호출하는 코드를 제거하고 다음과 같이 작성한다.
// add workRequest to cleanup
var continuation = workManager
.beginWith(OneTimeWorkRequest
.from(CleanupWorker::class.java))
// add workRequest to blur
val blurRequest = OneTimeWorkRequest.Builder(BlurWorker::class.java)
.setInputData(createInputDataForUri())
.build()
continuation = continuation.then(blurRequest)
// add workRequest to save image
val save = OneTimeWorkRequest.Builder(SaveImageToFileWorker::class.java).build()
continuation = continuation.then(save)
// Actually start the work
continuation.enqueue()
workManager.beginWith()를 호출하면 WorkRequest 체인을 정의하는 WorkContinuation이 반환된다. then() 메서드를 호출하여 이 작업 요청 체인에 추가할 수 있다.
blurViewModel의 blurRequest를 blurLevel에 따라 적용되도록 수정한다.
작업 체인을 한 번에 하나씩만 실행해야 하는 경우 beginWith 대신 beginUniqueWork를 사용하고 고유한 이름을 제공한다.
사진을 한 번에 한 장만 블러 처리하도록 코드를 수정한다.
var continuation = workManager
.beginUniqueWork(
IMAGE_MANIPULATION_WORK_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.from(CleanupWorker::class.java)
)
IMAGE_MANIPULATION_WORK_NAME는 키ExistingWorkPolicy 옵션은 REPLACE, KEEP, APPEND가 있는데 여기서는 현재 이미지가 완료되기 전에 다른 이미지를 블러 처리하려는 경우 현재 이미지가 중지되고 새 이미지가 블러 처리되도록 REPLACE를 사용한다.addTag를 추가해 태그를 지정한다.
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.addTag(TAG_OUTPUT)
.build()
LiveData<List<WorkInfo>>인 outputWorkInfos라는 새로운 변수를 선언하고,
BlurViewModel에서 WorkManager.getWorkInfosByTagLiveData를 사용하여 WorkInfo를 가져오는 init 블록을 추가한다.
WorkInfo의 LiveData를 가져왔으며 이제 BlurActivity에서 관찰할 수 있다.
WorkInfo가 null이거나 비어있으면 돌아간다.TAG_OUTPUT으로 태그된 WorkInfo를 가져온다.workInfo.state().isFinished()로 작업이 완료 상태인지 확인할 수 있다.Cancel Work 버튼을 눌렀을 때 작업이 취소되도록 한다. 이때 고유한 체인 이름으로 작업을 취소하는 것이 좋다.
internal fun cancelWork() {
workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
}
cancelButton에 클릭 리스너를 연결해 cancelWork()를 호출하도록 한다.
WorkManager는 Constraints를 지원한다. 예를 들어 이미지를 저장할 때는 기기를 충전해야 한다는 제약 조건을 사용할 수 있다.