WorkManager 는 모든 OS 백그라운드 실행 제한을 고려하여 백그라운드 실행에 권장되는 솔루션이다
사용 예시
지연가능한 작업의 보장된 실행 : WorkManager
서버에 로그 업로드하는 경우
업로드 / 다운로드 할 콘텐츠 암호화 / 복호화
외부 이벤트에 대한 응답으로 시작된 작업 : FCM + WorkManager
이메일과 같은 새로운 온라인 컨텐츠 동기화
Firebase Cloud Messaging을 사용해서 앱에 알리고
WorkManager로 작업 요청을 생성해 콘텐츠를 동기화 한다
모든 작업을 WorkManager를 사용하는 것은 올바른 사용 방법이 아니다
사용자가 현재 보고있는 UI를 빠르게 변경하는 작업이나 결제 진행 등 즉시 처리해야 하는 작업은 ForgroundService를 사용하거나 ThreadPool, Rx등을 사용해야 한다
WorkManager 구성
WorkManager API의 주요 클래스는 WorkManager, Worker, WorkRequest, WorkState이다
WorkManager
처리해야 하는 작업을 자신의 큐에 넣고 관리한다
싱클톤으로 구현이 되어있기 때문에 getInstance()로 WorkManager의 인스턴스를 받아 사용한다
Worker
추상 클래스이며, 처리해야 하는 백그라운드 작업의 처리 코드를 이 클래스를 상속받아 doWork() 메서드를 오버라이드 하여 작성하게 된다
doWork()
작업을 완료하고 결과에 따라 Worker클래스 내에 정의된 enum인 Result의 값중 하나를 리턴해야 한다
SUCCESS, FAILURE, RETRY의 3개 값이 있으며 리턴되는 이 값에 따라 WorkManager는 해당 작업을 마무리 할것인지, 재시도 할것인지, 실패로 정의하고 중단할 것인지를 결정하게 된다
WorkRequest
WorkManager를 통해 실제 요청하게 될 개별 작업이다
처리해야 할 작업인 Work와 작업 반복 여부 및 작업 실행 조건, 제약 사항등 이 작업을 어떻게 처리할 것인지에 대한 정보가 담겨있다
반복여부에 따라 onTimeWorkRequest, PeriodicWorkRequest로 나뉜다
onTimeWorkRequest
반복하지 않을 작업, 즉 한번만 실행할 작업의 요청을 나타내는 클래스
PeriodicWorkRequest
여러번 실행할 작업의 요청을 나타내는 클래스
WorkState
WorkRequst의 id와 해당 WorkRequest의 현재 상태를 담는 클래스
이 WorkState의 상태정보를 이용해서 자신이 요청한 작업의 현재 상태를 파악할 수 있다
ENQUEUED, RUNNING, SUCCEEDED, FAILED, BLOCKED, CANCLLED의 6개 상태를 가진다
1. 단순 작업
Worker 클래스를 상속받은 클래스를 만들고 doWork() 메서드를 오버라이드 한다
처리 결과에 따른 Result 값을 리턴해야 한다
1
2
3
4
5
6
7
8
importandroidx.work.WorkerclassExampleWorker:Worker(){overridefundoWork():Result{/* 처리해야할 작업에 관한 코드들 */returnResult.success()}}
OneTimeWorkRequestBuilder를 이용해서 OneTimeWorkRequest객체를 생성한다
1
2
3
4
5
/* 코틀린에 정의된 인라인 함수 OneTimeWorkRequestBuilder */varworkRequest=OneTimeWorkRequestBuilder<ExampleWorker>().build()/* 자바에서는 OneTimeWorkRequest 클래스내에 정의된 OneTimeWorkRequest.Builder를 사용해야 함 */varworkRequest=OneTimeWorkRequest.Builder(ExampleWorker::class.java)
WorkManager 클래스의 getInstance() 메서드로 싱글턴 객체를 받아서 WorkManager의 작업 큐에 OneTimeWorkRequest 객체를 추가해준다
반복되는 작업은 PeriodicWorkRequestBuilder를 이용해서 PeriodicWorkRequest객체를 생성한 뒤 WorkManager의 큐에 추가해주면 된다
1
2
3
4
5
6
/* 반복 시간에 사용할 수 있는 가장 짧은 최소값은 15 */valworkReqeust=PeriodicWorkRequestBuilder<ExampleWorker>(15,TimeUnit.MINUTES).build()valworkManager=WorkManager.getInstance()workManager?.enqueue(workRequest)
2. 제약 조건을 가지는 작업
해당 제약조건이 만족되면 작업을 수행하고, 조건이 만족되지 않으면 작업을 취소하며, 처리가 완료되지 못하고 실패한다면 제약조건이 만족되는 다음 타이밍에 다시 처리를 시도하게된다
제약 조건은 Constraints 클래스의 Builder를 이용해서 생성한 뒤 WorkRequest에 추가한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
valconstraints=Constraints.Builder()/* 네트워크 연결상태에 대한 제약 조건 */.setRequiredNetworkType(NetworkType.CONNECTED)/* 충전 상태에 대한 제약 조건 */.setRequiresCharging(true).build()/* 제약조건과 함께 작업을 생성하거나 */valrequestConstraint=OneTimeWorkRequestBuilder<ExampleWorker>().setConstraints(constraints).build()/* 작업을 생성하고 나중에 제약조건을 설정해 줄수 있다 */workRequest.setConstraint(constraints)
3. 연결된 작업
두 작업을 연결해서 처리하는 방법
각 작업을 WorkRequest로 만들어서 처음 실행될 작업을 WorkManager의 beginWith() 메서드의 인자로 추가하고, then() 메서드에 이어할 작업을 추가해준다
WorkManager는 workA를 수행하고 이 작업이 완료된 이후 workB 작업을 수행하게 된다
valworkRequest=OneTimeWorkRequestBuilder<ExampleWorker>().build()valworkManager=WorkManager.getInstance()workManager?.let{it.enqueue(workRequest)/** WorkManager의 getStatusById()에 WorkRequest의 UUID 객체를 인자로 전달 하면
* 인자값으로 주어진 ID에 해당하는 작업을 추적할 수 있도록 LiveData 객체를 반환한다
*/valstatusLiveData=it.getStatusById(workRequest.id)/* statusLiveData에 Observer를 걸어서 작업의 상태를 추적 */statusLiveData.observe(this,Observer{workState->Log.d("exmaple","state: ${workState?.state}")})}
5. 작업간 정보 전달
setInputData() 메서드를 통해서 Data객체를 인자로 Worker에 정보를 전달할 수 있다
1
2
3
4
5
6
7
valinput=mapOf("question"to"answer")/* Data클래스의 Builder를 사용해서 Data 객체를 생성한다 */valinputData=Data.Builder().putAll(input).build()valrequestWork=OneTimeWorkRequestBuilder<ExampleWork>().setInputData(inputData).build()
Worker클래스에는 전달받은 Data 객체를 반환하는 getInputData() 메서드가 있으며, 이 메서드를 통해 Data객체를 반환받아 사용한다
1
valquestion=inputData.getString("question","")
InputMeger를 이용하면 여러 작업에서의 정보 전달이 가능해진다
WorkManager의 기본 InputMeger는 OverwritingInputMerger이다
OverwritingInputMerger
여러개의 Data가 전달될 때 같은 Key를 가지는 value는 덮어 쓴다
ArrayCreatingInputMerger
여러개의 Data가 전달될 때 같은 key를 가지는 value를 배열로 전달한다
단 배열의 특성상 같은 Key의 value의 타입이 서로 다르면 배열을 만들 수 없기 때문에 exception이 발생한다
/* cancelWork 작업을 WorkManager의 큐에 추가 */WorkManager.getInstance()?.enqueue(cancelWork)/* cancelWork의 id를 이용해서 작업을 취소 */WorkManager.getInstnace()?.cancelWorkById(cancelWork.id)
UUID는 어려운 문자열이기 때문에 쉽게 접근하기 위해 태그를 달 수 있다
태그를 이용해 작업을 취소하면 해당 태그를 가진 모든 작업을 한번에 취소하는 기능을 하게 된다
1
2
3
4
valcancelWork=OnetimeRequestBuilder<CancelWorker>().addTag("cancel work tag")WorkManager.getInstnace()?.cancelWorkById("cancel work tag")
WorkManager에서 취소하고자 하는 작업이 이미 완료된 작업이라면 취소 메서드는 아무 기능도 하지 않는다
아직 실행 전 큐에 담긴 상태라면 실행하지 않고 취소된다
하지만 이미 실행된 작업을 임의로 멈추지는 않는다
그래서 작업 취소 플래그를 설정해 줄 수 있다
isStopped() == true
작업 중지 상태, isCancelled()가 false인 경우에는 시스템에 의한 작업이 중지된 경우 이므로 해당 작업은 다음 어느 시점에 다시 수행된다
isCancelled() == true
작업 취소 상태, 반드시 isStopped()가 true임을 확인 한 후 해당 플래그를 참고해야 한다
1
2
3
4
5
6
7
8
9
overridefundoWork():Result{if(isStopped){/* 작업이 멈추었을 때 대비한 코드 */if(isCancelled){/* 작업이 취소 되었을 때 대비한 코드 */}}}
7. 유일한 작업(Unique Work)
beginUniqueWork() 메서드를 통해 Unique Work 라는 작업 처리 방식을 제공한다
작업에 유일한 이름을 부여하고 이 이름을 통해서 큐에 넣거나, 조회하거나, 취소할 수 있다
같은 이름을 가지는 작업이 이미 WorkManager 큐에 존재하면 추가하려는 작업에 대한 동작 방식을 KEEP, REPLACE, APPEND의 세가지 중 하나로 지정할 수 있다
KEEP
작업 A가 실행 대기중 이거나 실행 중이면 작업B는 WorkManager의 큐에 추가 되지 않는다