지연 가능하고 보장돼야 하는 백그라운드 작업의 표준.
class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result = try {
repository.sync()
Result.success()
} catch (e: IOException) {
Result.retry() // 백오프를 두고 다시 시도한다
} catch (e: Exception) {
Result.failure() // 재시도해도 소용없는 실패
}
}
val request = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // 와이파이에서만
.setRequiresCharging(true)
.build())
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(ctx).enqueueUniquePeriodicWork(
"sync", ExistingPeriodicWorkPolicy.KEEP, request
)
무엇을 보장하나
-
앱이 죽어도 예약이 남는다 (내부 SQLite 에 기록된다)
-
재부팅을 넘는다
-
제약(네트워크·충전·배터리·유휴) 이 만족될 때까지 시스템이 기다린다
-
지수 백오프로 재시도한다
-
보장하지 않는 것: 정확한 실행 시각
- 최소 주기가 15분이고, Doze 모드에서는 더 미뤄진다
- "정확히 오전 9시" 가 필요하면 AlarmManager 다
고유 작업 — 중복을 막는다
ExistingWorkPolicy.KEEP 이미 있으면 새 요청을 버린다
REPLACE 기존을 취소하고 교체한다
APPEND 기존이 끝난 뒤 이어서 실행한다
enqueue() 만 쓰면 화면에 들어올 때마다 작업이 쌓인다 — 흔한 사고
체이닝
WorkManager.getInstance(ctx)
.beginWith(listOf(compressWorker, uploadThumbWorker)) // 병렬
.then(uploadWorker) // 둘 다 끝나면
.then(notifyWorker)
.enqueue()
무엇을 넘기나
val data = workDataOf("userId" to 42L) // Data 는 약 10KB 제한이다
큰 데이터는 넘기지 않는다 — id 만 넘기고 Worker 가 DB 에서 읽는다 결과도 마찬가지다
언제 쓰지 않나
- 화면이 살아 있는 동안만 필요 — → viewModelScope
- 사용자가 즉시 결과를 기다린다 — → viewModelScope (WorkManager 는 지연될 수 있다)
- 정확한 시각 — → AlarmManager
- 사용자가 인지하는 지속 작업 — → 포그라운드 서비스
면접 함정
- ❌ "WorkManager는 즉시 실행된다" → 시스템이 배치로 묶어 미룬다. 즉시성이 필요하면 잘못된 선택이다.
- ❌ "주기 작업 15분을 5분으로 줄인다" → 최소값이 15분이라 그 이하는 무시된다.