Kotlin

[Kotlin 실전 (7)] 서버 없이 굴러가는 앱의 심장: WorkManager로 백그라운드 푸시 제어하기

smileDeveloper 2026. 9. 20. 13:00
반응형
SMALL

초기 트래픽 0인 상태에서 서버 DB를 파는 낭비를 막고 유지 비용을 0원으로 만드는 1인 개발 전략에서, 유저의 재방문(리텐션)을 유도하는 백그라운드 로직은 필수적입니다. 기기 로컬 스토리지에 의존하는 앱에서 외부 서버의 FCM 푸시 없이 기기 자체적으로 매일 상태를 갱신하고 알림을 띄우려면 안드로이드 시스템의 WorkManager가 완벽한 해결책이 됩니다.

1. WorkManager의 핵심 역할

WorkManager는 앱이 종료되거나 기기가 재부팅되어도 반드시 실행되어야 하는 지연 가능한(Deferrable) 비동기 작업을 시스템 단에서 보장합니다.
* 배터리 최적화: 안드로이드 OS의 도즈 모드(Doze Mode)를 준수하며, 시스템 자원이 여유로울 때 작업을 일괄 처리하여 배터리 광탈을 막습니다.
* 로컬 푸시 트리거: 비싼 푸시 서버 없이도 앱 내부에서 정해진 시간마다 "우주에 별빛이 가득 찼어요! 수확할 시간입니다." 같은 로컬 알림을 띄워 유저의 접속을 유도합니다.

2. CoroutineWorker 실전 구현

코루틴 생태계와 결합된 CoroutineWorker를 상속받아 작업을 정의합니다. doWork() 함수는 자동으로 백그라운드 스레드에서 실행되므로 안전하게 로컬 DB(Room)에 접근할 수 있습니다.

class DailyReminderWorker(
    appContext: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {
        return try {
            // 1. Room DB에서 오늘 조약돌 수집 여부 확인
            // val hasCollectedToday = repository.checkTodayAction()
            
            // 2. 수집하지 않았다면 로컬 푸시 알림 발생
            showLocalNotification(
                title = "페블리(Pebbly)",
                message = "오늘의 조약돌을 던질 시간입니다 🏃‍♀️"
            )
            
            Result.success()
        } catch (e: Exception) {
            Result.retry() // 에러 발생 시 시스템이 알아서 재시도
        }
    }
}


3. 주기적 작업 스케줄링 (스케줄러 등록)

생성한 Worker를 안드로이드 시스템에 등록합니다. 매일 주기에 맞춰 백그라운드 로직이 돌도록 PeriodicWorkRequestBuilder를 사용합니다.

fun scheduleDailyReminder(context: Context) {
    // OS 배터리 방어 정책상 최소 주기는 15분 이상부터 가능합니다.
    val reminderRequest = PeriodicWorkRequestBuilder<DailyReminderWorker>(
        24, TimeUnit.HOURS
    ).build()

    // 중복 등록 방지를 위해 UniqueWork로 큐에 삽입
    WorkManager.getInstance(context).enqueueUniquePeriodicWork(
        "daily_pebble_reminder",
        ExistingPeriodicWorkPolicy.KEEP, 
        reminderRequest
    )
}

무거운 서버 인프라 없이도 안드로이드 시스템 깊숙이 안착하여 매일 유저의 바탕화면을 노크하는 자동화 파이프라인이 완성되었습니다.

안드로이드 앱이 메모리에 올라갈 때 가장 먼저 실행되는 심장부인 Application 클래스에 WorkManager를 이식하여, 유저가 앱을 켜든 끄든 완벽하게 돌아가는 자동화 사이클을 완성합니다.
단발성 화면(Activity)이 아닌 Application 단에서 백그라운드 작업을 등록하면, 앱이 재시작되거나 업데이트될 때마다 스케줄러가 누락 없이 백그라운드 큐(Queue)에 안전하게 안착합니다.

4. 커스텀 Application 클래스 생성

안드로이드의 기본 Application 클래스를 상속받아 onCreate() 시점에 앞서 만든 스케줄링 함수를 호출합니다. 기존에 선언한 ExistingPeriodicWorkPolicy.KEEP 정책 덕분에 앱을 여러 번 켜도 워커가 중복 등록되지 않고 우아하게 기존 스케줄을 유지합니다.

import android.app.Application

class PebblyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // 앱이 켜질 때마다 데일리 리마인더 스케줄링 확인 및 등록
        scheduleDailyReminder(this)
    }
}


5. Manifest에 심장 이식하기 (가장 중요)

아무리 커스텀 Application 클래스를 예쁘게 짜도, 안드로이드 운영체제에 이를 알려주지 않으면 시스템은 기본 클래스를 실행해버립니다. AndroidManifest.xml의 <application> 태그에 android:name 속성을 반드시 추가해야 합니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.smilestudio.pebbly">

    <!-- android:name 속성에 방금 만든 Application 클래스 지정 -->
    <application
        android:name=".PebblyApplication" 
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.Pebbly">
        
        <!-- 메인 액티비티 및 위젯 리시버 등 기존 세팅 유지 -->
        <activity android:name=".MainActivity" ... />
        
    </application>
</manifest>


6. Hilt/Koin (DI) 연동을 위한 확장성

1인 개발로 앱 덩치가 커질 경우, 백그라운드 Worker 내부에서 Room DB나 Repository에 접근하기 위해 의존성 주입(DI)이 필요해집니다. Application 클래스는 추후 @HiltAndroidApp 같은 어노테이션을 붙여 앱 전체의 데이터 베이스 싱글톤 객체를 공급하는 베이스캠프 역할까지 겸하게 됩니다.

앱의 생명주기를 완벽하게 통제하는 베이스캠프 세팅이 끝났습니다.


반응형
LIST