사용자에게 좀 더 신속한 데이터 갱신 처리를 제공하기 위해 백그라운드 동기화 처리를 구현하기로 했다.
Jetpack에서 지원하는 WorkManager를 사용해 보기로 했다.
먼저 build.gradle에 다음 설정을 추가해준다.
implementation "androidx.work:work-runtime-ktx:2.8.1"
그리고 작업 정의를 위한 Worker 클래스를 생성한다.
class SyncWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
syncAndCallApi()
return Result.success()
}
}
doWork 내에서 비동기 작업을 선언해 주고, Result를 통해 성공 여부를 상황에 맞게 반환할 수 있다.
Result.success(): 작업이 성공적으로 완료되었습니다.
Result.failure(): 작업에 실패했습니다.
Result.retry(): 작업에 실패했으며 재시도 정책에 따라 다른 시점에 시도되어야 합니다.
필자는 단순하게 작업 처리 후 Success로 처리했다.
WorkerParameters의 경우에는 Worker 클래스의 생성자에 전달되어 Worker의 동작을 제어하고 필요한 데이터를 전달하는 역할을 한다. WorkerParameters를 통해 Worker가 실행되는 환경과 관련된 정보에 접근할 수 있다.
이제 WorkRequest를 생성해야 한다.
일회성 작업의 경우에는 다음과 같이 간단하게 생성할 수 있다.
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
// Additional configuration
.build()
하지만 필자는 반복 작업 구현이 필요하여 PeriodicWorkRequest를 사용했다.
private fun createWorkRequest() =
PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES) // 15분마다 반복
.setConstraints(createConstraints())
.setInitialDelay(15, TimeUnit.MINUTES)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS
).build()
각종 설정을 적용했다.
먼저 인자로 들어가는 repeatInterval과 TimeUnit은 말 그대로 반복 주기와 단위를 설정해 주는 것이다.
최소 설정 가능한 반복 주기는 15분이다.
Constraints는 제약조건을 설정한다.
private fun createConstraints() = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.build()
UNMETERED는 와이파이 상황의 네트워크 타입을 의미한다.
데이터 비용 부담이 없도록 와이파이 상황에서만 비동기 처리가 진행되도록 했다.
NetworkType에는 다음과 같은 종류가 있어 상황에 맞게 선택 가능하다.
enum class NetworkType {
/**
* A network is not required for this work.
*/
NOT_REQUIRED,
/**
* Any working network connection is required for this work.
*/
CONNECTED,
/**
* An unmetered network connection is required for this work.
*/
UNMETERED,
/**
* A non-roaming network connection is required for this work.
*/
NOT_ROAMING,
/**
* A metered network connection is required for this work.
*/
METERED,
/**
* A temporarily unmetered Network. This capability will be set for networks that are
* generally metered, but are currently unmetered.
*
* Note: This capability can be changed at any time. When it is removed,
* [ListenableWorker]s are responsible for stopping any data transfer that should not
* occur on a metered network.
*/
@RequiresApi(30)
TEMPORARILY_UNMETERED
}
이 외에도 배터리 충전율, 유휴 상태, 저장 공간 여유 등에 따라 다양한 제약 조건 설정이 가능하다.
InitialDelay의 경우에도 15분으로 설정했다.
WorkManager에 작업이 등록되어도 바로 실행되는 것이 아니라 15분 뒤에 최초 작업 수행하도록 했다.
.setInitialDelay(15, TimeUnit.MINUTES)
작업이 모종의 이유로 실패할 경우, 다시 시도할 때 백오프 정책을 LINEAR 타입의 최소 백오프로 지정했다.
.setBackoffCriteria(
BackoffPolicy.LINEAR,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS
.
.
.
enum class BackoffPolicy {
/**
* 실패한 작업이 다시 시도될 때마다 백오프 시간이 증가하므로, 시간이 흐를수록 대기 시간이 길어진다.
*/
EXPONENTIAL,
/**
* 실패한 작업이 다시 시도될 때마다 일정한 시간 간격을 더해준다.
* 이는 각 시도 간에 백오프 시간이 일정하게 증가하므로, 예측 가능한 백오프 동작을 갖는다.
*/
LINEAR
}
이제 생성된 Request와 Worker를 통해 WorkManager에 주기적 반복 작업을 등록한다.
fun reserveWork() {
val workRequest = createWorkRequest()
workManager.enqueueUniquePeriodicWork(
SyncWorker::class.java.name,
ExistingPeriodicWorkPolicy.KEEP,
workRequest
)
}
여기서 ExistingPeriodicWorkPolicy.KEEP은 이미 예약된 작업이 있을 경우 신규 작업을 등록하지 않는다.
따라서 중복 작업 처리를 방지할 수 있다.
App Inspection의 Background Task Inspector를 통해 Workder의 작업 상태 확인이 가능하다.
조건에 따라 Worker 작업을 취소하는 함수 또한 생성했다.
fun cancelWork() {
workManager.cancelUniqueWork(SyncWorker::class.java.name)
}