Background Service 는 사용자 인터페이스 없이 백그라운드에서 장기 작업을 수행하도록 설계된 Android 컴포넌트입니다. Activity와 달리, Service는 앱이 최소화되거나 사용자가 다른 앱으로 전환해도 계속 작동합니다. Android Developers, 2026에 따르면, Started Service, Bound Service, Foreground Service의 세 가지 서비스 유형이 있으며, 각각 고유한 생명 주기와 적용 범위를 가집니다.
주요 포인트
Background Service(또는 간단히 Service)는 Activity, BroadcastReceiver 그리고 ContentProvider와 함께 Android 애플리케이션의 기본 컴포넌트 중 하나입니다. Activity와 달리, Service는 시각적 인터페이스가 없고 애플리케이션이 포그라운드에 있는지여와 관계없이 지속되어야 하는 작업을 수행하도록 설계되었습니다.
Service는 애플리케이션의 메인 스레드에서 실행되며, 따라서 내부의 블로킹 작업은 별도의 스레드가 필요합니다. 그렇지 않으면 시스템이 ANR(Application Not Responding)을 발생시킵니다. 간단한 백그라운드 작업을 위해 Android는 자동으로 워커 스레드를 생성하는 IntentService를 제공합니다. 현대 프로젝트에선 메인 스레드를 차단하지 않고 비동기 처리를 위해 Service 내에서 CoroutineScope와 함께 Kotlin 코루틴을 사용할 것을 권장합니다.
Service의 주요 목적은 음악 재생, 파일 다운로드, 네트워크 요청 처리, 데이터 동기화 그리고 사용자가 애플리케이션을 떠난 후에도 계속되어야 하는 다른 작업들입니다. 그러나 Android 8 이후로 개발자는 백그라운드 작업 제한을 고려하여 서비스 유형 간에 신중하게 선택해야 합니다.
Service는 Activity와 달리 고유한 생명 주기를 가집니다. onCreate, onStartCommand, onBind 그리고 onDestroy의 네 가지 핵심 메소드로 구성됩니다. 메모리 누수 없이 백그라운드 작업을 올바르게 구현하려면 이 사이클을 이해하는 것이 필수적입니다.
onCreate 메소드는 서비스가 생성될 때 한 번 호출됩니다. 여기서 타이머, 데이터베이스 연결, 소켓같은 리소스가 초기화됩니다. onStartCommand 메소드는 startService가 호출될 때마다 호출되며, 이미 실행 중인 서비스에 명령을 보낼 수 있습니다. 반환값은 재시작 시 시스템의 동작을 결정합니다.
class DownloadService : Service() {
override fun onCreate() {
super.onCreate()
initializeDownloader()
}
override fun onStartCommand(
intent: Intent?,
flags: Int,
startId: Int
): Int {
downloadFile(intent?.getStringExtra("url"))
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? = null
}
onBind는 bindService를 통해 서비스를 바인딩할 때 호출되며, 클라이언트 상호작용을 위한 IBinder 객체를 반환합니다. 이 메소드는 Bound Service에서만 사용됩니다. onDestroy는 서비스가 파괴되기 전의 마지막 호출입니다. 여기서 모든 리소스가 해제되고, 스레드가 중지되며, 작업이 취소됩니다.
Android는 세 가지 유형의 Service를 제공하며, 각각 특정 시나리오에 맞게 설계되었습니다. 잘못된 유형을 선택하면 애플리케이션이 불안정하거나 배터리가 소모될 수 있습니다.
Started Service는 startService를 호출하여 시작되며 stopSelf 또는 stopService를 호출할 때까지 실행됩니다. 즉시 실행이 필요한 작업에 적합합니다: 애너리틱스 전송, 이미지 처리, 단일 파일 다운로드 등. 작업을 마친 후 서비스는 스스로 중지됩니다.
Bound Service는 클라이언트-서버 인터페이스를 제공하여 Activity, Fragment 또는 다른 컴포넌트가 서비스와 상호작용할 수 있습니다. 서비스는 적어도 하나의 클라이언트가 바인딩되어 있는 동안 유지됩니다. 모든 클라이언트가 연결을 끔으면 서비스가 파괴됩니다. Bound Service는 양방향 통신이 필요한 작업, 예를 들어 음악 플레이어나 네비게이션에 유용합니다.
Foreground Service는 상태봉에 지속적인 알림이 있는 Started Service입니다. 시스템은 이러한 서비스를 활성으로 간주하며 메모리가 부족해도 종료시키지 않습니다. Foreground Service는 음악 재생, 오디오 녹음, 위치 추적 및 사용자에게 중요한 다른 작업에 필수적입니다.
| 파라미터 | Started | Bound | Foreground |
|---|---|---|---|
| 시작 | startService | bindService | startForeground |
| 수명 | stopSelf까지 | 클라이언트가 있는 동안 | stopForeground까지 |
| 알림 | 없음 | 없음 | 필수 |
| 종료 가능 | 예 | 예 | 아니오 |
| 예시 | 다운로드 | 플레이어 | 음악 |
Service 생성은 Service를 상속받는 클래스를 선언하고 AndroidManifest.xml에 등록하는 것으로 시작됩니다. 매니페스트에 등록하지 않으면 시스템이 서비스를 시작할 수 없고, startService 호출은 예외를 발생시킵니다.
// AndroidManifest.xml에 등록
@SuppressLint("ForegroundServiceType")
class SyncService : Service() {
override fun onStartCommand(
intent: Intent?,
flags: Int,
startId: Int
): Int {
startForeground(
NOTIFICATION_ID,
createNotification()
)
performSync(intent)
return START_NOT_STICKY
}
}
Activity 또는 Fragment에서 서비스를 시작하려면 서비스 클래스에 대한 명시적 참조가 있는 Intent를 사용합니다. Android 8부터는 Foreground Service에 매니페스트에서 FOREGROUND_SERVICE 권한이 필요합니다.
// Started Service 시작
val intent = Intent(this, SyncService::class.java)
intent.putExtra("action", "sync")
startService(intent)
// Foreground Service 시작 (Android 8+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
Android 8부터 (API 26) Google은 백그라운드 서비스에 대한 엄격한 제한을 도입했습니다. 백그라운드 서비스 시작(애플리케이션이 포그라운드에 없을 때)은 예외적인 경우에만 허용됩니다: 퓨시 알림 수신 시, 기기 부팅 후, 또는 JobScheduler를 통해서만 가능합니다.
즉시 실행이 필요하지 않은 장기 작업에는 WorkManager 또는 JobScheduler를 사용할 것을 권장합니다. 애플리케이션에 실제로 실행 중인 서비스가 필요한 경우, 유일한 방법은 사용자가 볼 수 있는 알림이 있는 Foreground Service입니다. 백그라운드에서 알림 없이 서비스를 시작하면 시스템이 무시합니다.
JobIntentService는 Android 5+에서 작동하도록 지원 라이브러리에 나타난 전문 클래스입니다. IntentService의 동작(자동 워커 스레드, 순차 처리)과 JobScheduler를 통한 스케줄링을 결합합니다. Android 8+에서 JobIntentService는 내부적으로 JobScheduler를 사용하고, 구버전에서는 일반 Service를 사용합니다. 이를 통해 추가적인 Android 버전 확인 없이 백그라운드 작업을 일관되게 처리할 수 있습니다.
class UploadJobService : JobIntentService() {
companion object {
private const val JOB_ID = 1000
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(
context,
UploadJobService::class.java,
JOB_ID,
work
)
}
}
override fun onHandleWork(intent: Intent) {
val fileUri = intent.getStringExtra("file_uri")
// 백그라운드 스레드에서 실행됨
uploadFile(fileUri)
}
}
Background Service로 작업할 때 발생하는 일반적인 문제 중 하나는 메모리 누수입니다. Service가 Activity보다 오래 살 수 있기 때문에, Service 내에서 Activity에 대한 참조(listener, callback 또는 broadcast 통해)는 UI 컴포넌트의 가비지 컬렉션을 방지합니다. Service-통-UI 통신에는 WeakReference, ViewModel 또는 LiveData를 사용할 것을 권장합니다. onDestroy에서는 모든 구독을 취소하고, 스레드를 중지하며, 커서를 닫는 것을 잊지 마세요.
Background Service와 WorkManager 사이의 선택은 시나리오에 따락니다. Service는 즉시 그리고 지속적으로 실행되어야 하는 작업에 적합합니다: 음악 재생, 오디오 녹음, GPS 추적 등. WorkManager는 지연된, 보장된 작업에 더 좋습니다: 동기화, 애너리틱스 전송, 로그 업로드 등. WorkManager는 기기 재시작에도 건집되지만, Service는 그렇지 않습니다. Service는 알림으로 Foreground가 될 수 있지만, WorkManager는 백그라운드에서 조용히 작동합니다. 실제로 개발자는 두 가지 접근 방식을 결합합니다: 사용자에게 중요한 작업에는 Foreground Service, 백그라운드 유지보수에는 WorkManager를 사용합니다.
Android 12는 android:foregroundServiceType 플래그를 도입하여 서비스 유형을 지정하도록 요구합니다: dataSync, camera, connectedDevice, location, mediaPlayback 등. 잘못된 유형 지정은 시시작시 예외를 발생시킵니다. 이 실천은 Background Service를 사용자와 시스템 모두에게 더 투명하게 만듭니다.
매니페스트에서 Service를 올바르게 등록하려면 exported 속성(외부 앱에 대한 접근 가능성), foregroundServiceType(Android 12+에서 백그라운드 서비스 유형) 그리고 권한이 포함됩니다. Bound Service의 경우 JobIntentService에 대해 android:permission="android.permission.BIND_JOB_SERVICE"도 선언해야 합니다. 매니페스트에 등록하지 않으면 startService 또는 bindService 호출이 예외를 발생시키기 때문에, Service 문제 진단의 첫 단계는 매니페스트 확인입니다.
자주 묻는 질문
Service는 애플리케이션의 메인 스레드(UI Thread)에서 실행됩니다. onStartCommand 또는 onHandleIntent 내의 블로킹 작업은 별도의 스레드 또는 코루틴으로 옮겨야 하며, 그렇지 않으면 시스템이 5초 후 ANR을 발생시킵니다.
IntentService는 Service의 하위 클래스로 자동으로 워커 스레드를 생성하고 명령을 순차적으로 처리합니다. 마지막 작업을 완료한 후 IntentService는 스스로 중지됩니다. Android 8부터 IntentService는 JobIntentService 또는 WorkManager를 위한 구식으로 간주됩니다.
Android 12에서 백그라운드에서 Started Service를 시작하는 것은 금지됩니다. 예외는 매니페스트에 선언된 foregroundServiceType과 유효한 알림이 있는 Foreground Service입니다. 또한 높은 우선순위 FCM 메시지를 수신한 후 짧은 시작도 허용됩니다.
세 가지 방법이 있습니다: 로컬 브로드캐스트와 함께 BroadcastReceiver, Handler를 통한 Messenger 메커니즘, 그리고 공유 ViewModel이 있는 MVVM 아키텍쳐에서 LiveData/Flow를 사용하는 것입니다. Bound Service의 경우 IBinder를 사용하여 메소드를 직접 호출합니다.
Service가 START_STICKY 플래그로 시작된 경우, 메모리 부족으로 프로세스가 종료된 후 시스템이 재시작합니다. START_NOT_STICKY 플래그는 시스템이 서비스를 재시작하지 않는 것을 의미합니다. START_REDELIVER_INTENT는 START_STICKY와 유사하지만 마지막 Intent를 전달합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.