코딩

[코루틴 (11)] WorkManager + 코루틴 연동

Eastpark 2025. 1. 29. 19:11
728x90
반응형

지난 10편에서는 코루틴 고급 주제 & 퍼포먼스 최적화를 다루며, CoroutineContext 커스터마이징Dispatcher 튜닝, DebugProbes 같은 고급 기법을 살펴보았습니다. 이번 글에서는 WorkManager코루틴을 결합하여, 안드로이드 백그라운드 작업을 효율적으로 스케줄링하고 실행하는 방법을 알아보겠습니다.

 

핵심 키워드

WorkManager

CoroutineWorker

백그라운드 작업 스케줄링

작업 취소 & 재시작

CoroutineScope in WorkManager


1. WorkManager 간단 복습

 

1.1 백그라운드 작업 스케줄링

 

WorkManager는 안드로이드에서 지속적이고 안정적인 백그라운드 작업을 예약(스케줄링)하기 위한 Jetpack 라이브러리입니다.

OS 상태(배터리, 네트워크)에 따라 작업 실행 조건을 설정할 수 있고,

앱이 종료되어도 작업이 재시작될 수 있으며,

Firebase JobDispatcher나 AlarmManager를 직접 다루는 복잡도를 크게 줄여줍니다.

 

1.2 기존 Worker vs. CoroutineWorker

Worker: 기존에는 doWork() 메서드를 오버라이드하고, 내부에서 동기적으로 작업을 수행했습니다.

CoroutineWorker: 코루틴을 활용해 suspend fun doWork()를 오버라이드할 수 있으므로, 네트워크, DB 등을 좀 더 직관적으로 처리하고, suspend 함수 전체에서 일시 중단과 예외 처리가 가능해집니다.


2. CoroutineWorker 구조

 

2.1 기본 사용법

class MyCoroutineWorker(
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

    override suspend fun doWork(): Result {
        return try {
            // 실제 작업 로직 (네트워크, DB 등)
            fetchAndSaveData()
            Result.success()
        } catch (e: Exception) {
            Result.retry() // 혹은 Result.failure()
        }
    }
}

1. CoroutineWorker를 상속한다.

2. doWork() 메서드를 suspend 함수로 정의해, 코루틴 API(delay, IO 작업, Flow 등)와 자연스럽게 결합한다.

3. 작업이 실패하면 Result.retry(), 영구적 실패면 Result.failure(), 성공 시 Result.success()를 반환한다.

 

2.2 WorkRequest 생성 & 큐에 등록

val workRequest = OneTimeWorkRequestBuilder<MyCoroutineWorker>()
    .build()

WorkManager.getInstance(context)
    .enqueue(workRequest)

OneTimeWorkRequest 혹은 PeriodicWorkRequest를 만들어 WorkManager에 등록(enqueue)하면, 조건(네트워크, 배터리 등)에 따라 백그라운드에서 MyCoroutineWorker가 실행됩니다.

반응형

3. 백그라운드 작업과 코루틴 취소

 

3.1 WorkManager 취소 시나리오

 

WorkManager는 자체적으로 작업을 취소할 수 있습니다. 예를 들어, WorkRequest를 cancel하거나, 조건(네트워크 불가 등)이 불충족되면 작업이 중단될 수 있습니다.

CoroutineWorker에서는 코루틴 방식의 취소를 지원하며, isStopped 상태가 되면 doWork()CancellationException을 던질 수 있습니다.

override suspend fun doWork(): Result {
    for (i in 1..10) {
        if (isStopped) {
            return Result.failure() // 혹은 Result.retry()
        }
        // 예: 코루틴 delay
        delay(500)
    }
    return Result.success()
}

isStopped를 체크하거나, ensureActive() 같은 방법으로 취소 상태를 확인할 수도 있습니다.

WorkManager가 작업 취소를 요청하면, 내부적으로 해당 CoroutineScope가 캔슬되고, CancellationException이 발생합니다.

 

3.2 재시작 처리

Result.retry()를 반환하면, WorkManager는 BackoffPolicy에 따라 작업을 일정 시간 후 재시도합니다.

앱이 재부팅되거나, 네트워크가 다시 연결되는 등 환경이 바뀌면 Worker가 재시작될 수 있으므로, idempotent(멱등)한 작업 설계를 고려해야 합니다. 예: 중간 상태를 DB에 저장해 중복 작업을 피하는 방식.


4. WorkManager + 코루틴 실무 예시

 

4.1 실제 코드 샘플

class SyncDataWorker(
    context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

    override suspend fun doWork(): Result {
        return try {
            val apiResult = syncRemoteData() // suspend fun
            if (apiResult.isSuccessful) {
                updateLocalDb(apiResult.data)
                Result.success()
            } else {
                Result.retry()
            }
        } catch (e: Exception) {
            // 네트워크 오류, Parsing 오류 등
            Result.failure()
        }
    }
}

1. syncRemoteData(): Retrofit 코루틴 API로 서버 데이터를 가져옴

2. updateLocalDb(): Room + 코루틴 사용

3. 실패 시 Result.retry(), 치명적 에러 시 Result.failure()

4. 이 모든 과정을 코루틴으로 처리하므로, suspend 함수들의 일시 중단과 예외 처리가 간단해진다.

 

4.2 Work Constraints & CoroutineWorker

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .build()

val workRequest = OneTimeWorkRequestBuilder<SyncDataWorker>()
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(context).enqueue(workRequest)

NetworkType.CONNECTED 등으로 “네트워크가 연결된 상태”에서만 실행되도록 설정할 수 있습니다.

CoroutineWorker는 이 조건이 충족될 때 “일회성 작업”으로 실행되고, 조건이 사라지면 자동 취소될 수 있습니다.

728x90

5. Lifecycle & ForegroundService 고려

 

5.1 ForegroundService가 필요한 경우

백그라운드에서도 사용자에게 진행 상황(예: 파일 다운로드)을 알리며, 작업을 중단하지 않고 계속 유지해야 한다면 ForegroundService가 필요할 수 있습니다.

WorkManager는 setForegroundAsync()를 통해 ForegroundService 모드를 지원합니다.

override suspend fun doWork(): Result {
    setForeground(createForegroundInfo())
    // ...
    return Result.success()
}

createForegroundInfo()에서 알림(Notification) 정보를 생성해, 백그라운드 작업이 중단 없이 진행될 수 있도록 합니다.

 

5.2 LifecycleScope와 WorkManager 구분

LifecycleScope(예: ViewModelScope, Activity Scope)와 달리, WorkManager는 앱 프로세스와 UI 생명주기와 분리되어 오랫동안 동작 가능합니다.

UI에서 LifecycleScope로 처리해도 되는 짧은 작업은 굳이 WorkManager를 쓸 필요가 없지만, “몇 시간 후 재시도”, “기기 재부팅 후 재시작” 같은 시나리오가 필요하다면 WorkManager와 CoroutineWorker가 유용합니다.


6. 테스트 & 디버깅

 

6.1 Instrumented Test

WorkManager 테스트 시, TestDriver API로 Trigger를 인위적으로 호출하거나, 특정 조건(네트워크 연결/배터리 상태)을 모킹하여 시뮬레이션할 수 있습니다.

CoroutineWorker 내부의 suspend 로직은 9편에서 다룬 코루틴 테스트 기법과 유사하게 접근하되, 실제 WorkManager 스케줄링 과정을 통합적으로 검증하려면 Instrumented Test가 필요할 수 있습니다.

 

6.2 로그/디버거

CoroutineWorker도 코루틴이므로, DebugProbes나 IDE Coroutine Debugger를 통해 상태를 파악할 수 있습니다.

작업 중단 시점, 예외 발생 시점 등을 로깅하여, WorkManager가 Retry, Failure로 처리하는 과정을 추적합니다.


마무리 및 다음 예고

 

이번 [11편]에서는 WorkManager + 코루틴 연동에 대해 살펴보았습니다.

CoroutineWorker를 통해 백그라운드 작업을 suspend 함수 기반으로 작성

조건(Constraints)을 설정해 네트워크 연결 시에만 실행하거나, 필요 시 ForegroundService와 결합

취소·재시작 로직(멱등성), 장기 스케줄링 시나리오 등에 주의

 

다음 12편에서는 LifecycleScope & 안드로이드 생명주기 대응을 주제로, UI 컴포넌트(Activity/Fragment)의 생명주기와 코루틴을 자연스럽게 연동해 메모리 누수취소 시점을 자동으로 관리하는 방법을 알아보겠습니다.


시리즈 전체 목차

0. 코루틴 탄생 배경과 기본 개념

1. 코루틴 기초(suspend, CoroutineScope, Dispatcher)

2. Structured Concurrency와 코루틴 스코프 관리

3. 예외 처리와 취소(Cancellation) 기법

4. 코루틴 채널(Channel)과 select

5. Flow 심화(Flow, StateFlow, SharedFlow)

6. 안드로이드 실무 적용(Retrofit, Room, ViewModel)

7. 동시성 문제 해결(Mutex, Semaphore)

8. 코루틴 테스트 & 디버깅 전략

9. 고급 퍼포먼스 최적화

10. WorkManager + 코루틴 연동

11. LifecycleScope & 안드로이드 생명주기 대응

12. Compose + 코루틴 + Flow 시너지

13. Compose Navigation + 코루틴 취소/재시작 사례

728x90
반응형