[Android] Example of WorkManager. (+ RxJava or Coroutine) WorkManager를 사용하는 이유.
목차
- 소개
- WorkManager의 이점
- WorkManager가 적합한 작업
- Dependency 추가
- AndroidManifest.xml 파일 <provider> 수정
- 필요한 파일 생성
- 1. Worker Class 파일을 생성합니다.
- 2. Module 생성 (Only Koin)
- 3. Koin Initialize (Only Koin)
- 4. MainActivity에서 WorkManager 생성
- WorkManager를 구성하는 주요 요소
- Worker
- WorkRequest
- 결론
소개
WorkManager는 상황별 실행과 보장된 실행을 조합하여 적용해야 하는 백그라운드 작업을 위해 권장되는 솔루션입니다. 상황별 실행을 적용하면 WorkManager가 최대한 빨리 백그라운드 작업을 실행합니다. 보장된 실행을 적용하면 WorkManager가 사용자가 앱을 벗어난 경우를 비롯한 다양한 상황에서 로직을 처리하여 작업을 시작합니다. 앱이 다시 시작되거나 시스템이 재부팅될 때 작업이 예약된 채로 남아있으면 작업이 유지됩니다.
예를 들어, 메모를 기록한다고 했을 때, 메모장 앱이 종료되더라도 기록한 메모가 남아 있어야 합니다. 이런 경우 포그라운드에서 메모를 저장하기보다는 백그라운드에서 메모를 처리하도록 할 수 있습니다.
WorkManager의 이점
- 비동기 일회성 작업, 주기적인 작업 모두 지원
- 네트워크 상태, 저장공간, 충전 상태와 같은 제약 조건 지원
- 동시 작업 실행을 포함한 복잡한 작업 요청 체이닝 지원
- 한 작업 요청의 출력이 다음 작업 요청의 입력으로 사용될 수 있음
- UI 작업 요청 상태를 쉽게 표시할 수 있는 LiveData 지원
WorkManager가 적합한 작업
위의 이점들을 토대로 WorkManager가 가장 적합한 작업은 앱이 종료되어도 수행 해야 하고 지연 가능한 백그라운드 작업입니다. (it’s best for background work that has to finish and is deferrable.) WorkManager는 사용자가 특정 화면이나 앱에서 나가더라도 완료하는 것이 좋은 작업에 적합합니다.
예를 들어, 완료되어야 하는 작업이란, 기록한 메모를 백그라운드에서 반드시 저장하고 동기화해야 하는 것이 있을 수 있습니다. OS가 메모리를 회수하기 위해 앱을 닫아야 하는 경우, 장치를 다시 시작하는 경우에도 이러한 작업이 완료되어야 합니다. 다음과 같은 작업들이 있습니다.
- 애플리케이션 로그 업로드
- 이미지에 필터 적용 및 이미지 저장
- 주기적으로 로컬 데이터를 네트워크와 동기화
Dependency 추가
Application에서 Koin, RxJava를 사용하고 있기 때문에 이에 맞는 implementation을 추가하였습니다. 단순히 WorkManager만 사용한다면 위에 첫 줄만 작성해도 무방합니다. (Coroutine, Hilt 또한 지원하고 있습니다. 관련된 dependency를 추가하여 사용할 수 있습니다.)
AndroidManifest.xml 파일 <provider> 수정
WorkerFacktory를 커스텀 초기화 할 것이기 때문에 Default 초기화를 하지 않겠다고 AndroidManifest.xml에 명시해야 합니다.
이렇게 작성하고 빌드를 누르면 에러가 발생하는 경우가 있습니다.
다중 Manifest 파일을 병합하는 과정에서 발생하는 오류입니다. 다중 Manifest 파일이라 걸릴 만한 부분은 application ID 부분입니다. Clean Architecture로 앱을 디자인하였기 때문에 app, data, domain 으로 나뉘어 있는데 이는 세 개의 application ID가 있음을 뜻합니다. 안드로이드 스튜디오가 제안한 대로 “tools:replace=androud:authorities”를 추가하여 오류를 해결할 수 있습니다.
필요한 파일 생성
1. Worker Class 파일을 생성합니다.
class NetworkWorker(context: Context, params: WorkerParameters) : RxWorker(context, params), KoinComponent {
private val somethingInject by inject() // Optional
override fun createWork(): Single<Result> {
return try {
// Do something
somethingInject.apiCall()
.doOnSuccess {
// Do Something
}.map {
Result.success()
}.onErrorReturn {
Result.failure()
}
} catch (e: Exception) {
Single.just(Result.failure())
}
}
}
Work Manager를 RxJava와 같이 사용해야 한다면 RxWorker를 상속받습니다. (일반적인 경우 Worker를 상속받을 수 있고, 코루틴을 같이 쓴다면 CoroutineWorker를 상속받으면 됩니다.) RxWorker는 createWork를 반드시 구현해야 합니다. RxJava 답게 리턴 값은 Single<Result>입니다.
by inject()를 사용하여 Koin 의존성 주입을 사용할 수 있습니다. 저는 usecase를 주입하였고 해당 함수가 성공했을 때 필요한 처리는 doOnSucess {}에서 처리하면 되겠습니다.
2. Module 생성 (Only Koin)
val workManagerModule = module {
worker { NetworkWorker(get()) }
}
저는 Koin을 사용하였기 때문에 module을 생성해 주어야 의존성 외부 주입을 사용할 수 있습니다. module을 정의합니다. 두 번의 by inject 문을 사용하였기 때문에 module 안에 두 개의 single 객체 주입을 선언하였습니다.
3. Koin Initialize (Only Koin)
startKoin {
...
workManagerFactory()
modules(
...
workManagerModule
)
}
작성한 module을 startKoin {}에 선언합니다. 해당 함수는 Application()에서 작업합니다. 마찬가지로 workManagerFacktory() 함수 또한 반드시 선언해야 하는 함수입니다.
4. MainActivity에서 WorkManager 생성
private fun setNetworkWorker() {
val workManager = WorkManager.getInstance(this)
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 네트워크 연결 상태를 설정할 수 있다. (Only WIFI, Data etc.)
.build()
val workRequest = PeriodicWorkRequestBuilder<NetworkWorker>(
30, TimeUnit.MINUTES,
15, TimeUnit.MINUTES
).setConstraints(constraints).build()
workManager.enqueueUniquePeriodicWork(
"network-worker",
ExistingPeriodicWorkPolicy.KEEP,
workRequest
)
}
저는 로컬 데이터와 서버 데이터 동기화를 위해 WorkManager를 사용한 것이기 때문에 PeriodicWorkRequestBuilder를 선언하였습니다. 앱에 저장되어 있는 로컬 데이터가 있다면 한 시간마다 체크해서 네트워크로 전송합니다. 필요한 요소를 정의한 후 WorkManager Queue에 enqueue 하면 Work Manager가 정상적으로 동작하게 됩니다.
WorkRequest를 보면 repeat Interval이 있고 flex time interval이 있습니다. repeat interval은 최소 15분, flex time interval의 최소 시간은 5분입니다. 최소 시간은 변경이 불가능하기 때문에 이보다 낮게 설정할 경우 log, output을 보기 위해서는 최소 시간만큼 일정 시간 더 기다려야 볼 수 있습니다. 또한, enqueueUniquePeriodicWork를 사용해서 현재 작업 중인 Work Manager에 태그를 지정하고 이후에 있을 확장성에 대해서도 고려하여 작업하였습니다.
기본적으로 repeat 시간 간격을 갖고 작업이 반복됩니다. 만약, 어떤 작업의 특성상 타이밍이 중요하다면 flex 시간을 설정해서 repeat(flex time 포함) 이내에 반드시 실행되도록 PeriodicWorkRequest를 구성할 수 있습니다. 만약 repeat이 15분이고 flex가 5분이라면 15분에 간격 중 끝에 5분 내에 PeriodicRequest가 실행된다는 것을 의미합니다.
WorkManager를 구성하는 주요 요소
Worker
- WorkManager API에서 What을 담당하는 부분. 무엇을 할 것인지 Worker Class를 상속받은 클래스 파일 안에 정의합니다.
WorkRequest
- WorkRequest가 반복할 수 있는 최소 시간은 15분입니다.
- WorkManager API에서 When, How를 담당하는 부분. 한 번만 할 것인지 지속적으로 확인할 것인지 등을 정의합니다.
- 추가적으로 Constraints를 정의하여 추가할 수도 있습니다. 현재 배터리 충천 상태인지 네트워크 연결 상태인지 등을 체크할 수 있습니다.
결론
Hilt, Koin, RxJava, Coroutine 등 구글 공식 문서에서 지원하고 있는 라이브러리와 함께 Work Manager를 사용할 수 있을 것이라고 생각됩니다. 사용하기 전에 이론을 찾아볼 때는 양이 상당히 많아서 복잡할 것 같아 보였는데, 막상 실제로 사용해 보니 매우 깔끔하고 쉽게 사용할 수 있었습니다. 앱이 종료되어도 지속적으로 체크를 하기 때문에 로컬에 저장된 데이터를 서버와 동기화시키는 데에 매우 유용하게 사용할 수 있겠습니다.
앱 아키텍처: 데이터 영역 - WorkManager로 작업 예약 - Android 개발자 | Android Developers
WorkManager를 사용한 백그라운드 작업 - Kotlin | Android Developers
작업 요청 정의 | Android 개발자 | Android Developers
android - WorkManager not repeating the PeriodicWorkRequest - Stack Overflow