오래 지속되는 작업을 백그라운드에서 처리하기 위해 사용되는 앱 컴포넌트
사용자 인터페이스를 제공하지 않음
주의: 서비스는 프로세스의 메인 스레드에서 실행됨, 서비스는 특별히 명시하지 않는 한 스레드를 생성하지 않고, 별도의 프로세스에서 실행되지 않음, 따라서
ANR(Application Not Respond)
를 피하려면blocking
작업은 별도의 스레드에서 실행되어야 함
시작된 서비스와 연결된 서비스를 분리해서 얘기하지만, 동시에 해당할 수 있음, onStartCommand()
를 구현해 시작할 수 있고, onBind()
를 구현해 연결할 수 있음
서비스는 컴포넌트가 액티비티를 시작하는 것과 같은 방식으로 인텐트를 사용해 시작할 수 있음
매니페스트 파일에 서비스를 선언할 때 private
으로 선언해 다른 앱의 접근을 막을 수 있음
사용자가 인지할 수 있는 작업을 하는 서비스
예시로, 음악 앱에서 음악을 재생하는 작업을 포그라운드 서비스로 사용할 수 있음
사용자에게 서비스가 실행되어 있다는 것을 알리기 위해 실행중에 항상 알림(notification
)을 띄워야 함
노트:
WorkManager
API는 작업(task
)을 예약하는 유연한 방법을 제안함, 필요하면 작업을 포그라운드 서비스로 실행시킬 수 있음. 대부분의 경우, 포그라운드 서비스를 직접 사용하는 것보다WorkManager
를 사용하는 것이 바람직합니다.
사용자가 인지하지 못하는 작업을 수행하는 서비스
예시로, 저장소를 압축하는 서비스가 있으면 백그라운드 서비스로 사용함
노트: 앱이 API 레벨 26 이상을 타겟팅한다면, 시스템은 포그라운드에 있지 않은 앱이 백그라운드 서비스를 실행하는 것을 제한시킵니다. 예를 들어 백그라운드에서 위치 정보에 접근하면 안됩니다. 대신
WorkManager
를 사용해 작업을 스케줄하세요.
앱 컴포넌트가 bindService()
를 호출할 때 연결됨
서비스와 상호작용하고, 요청을 보내고, 결과를 수신하고, IPC를 통해 프로세스 간 이런 작업을 할 수 있는 클라이언트-서버 인터페이스를 제공함
다른 연결된 앱 컴포넌트가 있을 동안만 실행됨
여러개의 컴포넌트가 동시에 한 서비스에 연결될 수 있지만, 모든 컴포넌트에 연결이 해제되면 서비스는 파괴됨
서비스는 사용자가 앱과 상호작용하지 않을 때에도 백그라운드에서 처리할 일이 있다면 사용해야 함
메인 스레드 밖에서 처리할 일이 있지만, 사용자가 앱과 상호작용하는 동안에만 수행해야 할 경우 서비스를 사용하는 대신 다른 컴포넌트에서 스레드를 만들어 처리할 수 있음
예를 들어, 액티비티가 실행될 동안에 음악을 재생해야 한다면 스레드를 onCreate()
에 만들고 onStart()
에서 실행함, 그리고 onStop()
에서 멈추면 됨, 이 때 스레드 풀과 java.util.concurrent
패키지의 실행자(executor
)를 사용하거나 기존의 Thread
클래스 대신 코루틴을 사용하는 것을 고려해보면 좋음
서비스는 기본적으로 앱의 메인 스레드에서 실행됨, 서비스에서 blocking
작업을 할 때 새 스레드를 만들어야 함
Service
클래스를 상속받는 클래스를 만들어 클래스를 만들 수 있음
서비스의 수명주기에 맞게 동작하게 하거나, 다른 컴포넌트와 연결되게 하려면 반드시 구현해야 하는 콜백 메소드들이 있음
onStartCommand()
startService()
로 서비스를 시작하면 시스템이 호출하는 메소드stopSelf()
또는 stopService()
를 호출해 서비스를 멈출 책임이 있음onBind()
bindService()
로 서비스와 연결할 때 시스템이 호출하는 메소드IBinder
를 반환해야 함null
을 반환하면 됨onCreate()
onStartCommand()
나 onBind()
가 불리기 이전) 시스템이 첫 설정 과정을 수행하기 위해 실행하는 메소드onDestroy()
서비스가 startService()
에 의해 시작되면(onStartCommand()
가 불림) 스스로 stopSelf()
를 실행하거나 다른 컴포넌트에서 stopService()
를 실행하기 전까지 실행됨
서비스가 bindService()
에 의해 시작되면 onStartCommand()
는 호출되지 않음, 서비스는 컴포넌트에 연결되는 동안만 실행됨, 모든 컴포넌트와의 연결이 끊기면 시스템은 서비스를 파괴함
안드로이드 시스템은 사용자가 포커스를 가지고 있는 액티비티가 필요한 시스템 자원이 부족하지 않은 한 서비스를 중단하지 않음, 사용자가 포커스를 가지고 있는 액티비티와 연결된 서비스는 중단될 일이 거의 없음, 포그라운드에서 실행되는 서비스는 가끔 중단됨, 서비스가 시작되어있고 실행한지 오래 되었다면, 시스템은 시간에 따라 우선순위를 낮춰 서비스가 중단될 확률을 높임
시스템이 서비스를 중단시키면 자원을 사용 가능해질 때 서비스를 다시 시작하지만, onStartCommand()
의 반환 값에 따라 달라짐
다른 컴포넌트와 같이 앱의 모든 서비스를 매니페스트 파일에 선언해야 함
<service>
요소를 <application>
요소의 자식으로 추가해 서비스를 선언함
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
서비스를 시작하기 위해 필요한 권한과 같은 요소를 <service>
요소에 속성으로 포함시킬 수 있음
android:name
속성이 유일하게 필수인 속성임, 이 속성은 해당 서비스의 클래스 이름을 명세함, 앱을 배포한 이후에 이 이름을 교체하면 명시적 인텐트로 서비스를 시작하거나 연결할 때 문제가 생길 수 있으므로 교체하지 않아야 함
주의: 앱의 안정성을 확보하려면 서비스를 시작할 때 항상 명시적 인텐트를 사용하고, 서비스에 인텐트 필터를 지정하지 않아야 합니다. 암시적 인텐트로 서비스를 시작하는 것은 어떤 서비스가 인텐트에 응답할 지 확신할 수 없고 사용자가 어떤 서비스가 시작되는지 인지하지 못하기 때문에 보안 위험입니다. API 레벨 21 이상부터
bindService()
를 암시적 인텐트로 호출하면 시스템에 예외가 발생합니다.
android:exported
속성을 false
로 설정해 해당 앱에서만 서비스를 사용할 수 있게 보장할 수 있음, 이는 다른 앱이 서비스를 시작하는 것을 효율적으로 막을 수 있음, 명시적 인텐트를 사용해도 다른 앱이 사용할 수 없게 됨노트: 사용자는 장치에 어떤 서비스가 실행중인지 볼 수 있습니다. 만약 사용자가 인지하지 못하거나 믿지 못하는 서비스를 보게 되면 그 서비스를 중지 시킬 수 있습니다.
android:description
속성을<service>
요소에 추가해 서비스가 어떤 일을 하는지 짧은 문장을 제공해 사용자가 실수로 서비스를 중단시키는 일을 피할 수 있습니다.
다른 컴포넌트가 startService()
를 통해 서비스를 시작하면 onStartCommand()
가 불림
서비스가 시작되면 서비스를 시작한 컴포넌트와 독립적인 서비스만의 수명주기가 생김
서비스를 시작한 컴포넌트가 파괴되어도 서비스는 백그라운드에서 계속 실행될 수 있음, 따라서 작업이 끝나면 스스로 stopSelf()
를 부르거나 또는 다른 컴포넌트에서 stopService()
를 불러 서비스를 중단시켜야 함
액티비티와 같은 앱 컴포넌트는 startService()
에 어떤 서비스를 사용하는지와 서비스에서 필요한 정보를 담은 Intent
를 전달해 서비스를 시작할 수 있고 이 때 Intent
는 서비스의 onStartCommand()
에 전달됨
주의: 서비스는 선언된 앱의 동일한 프로세스에서 실행되며, 기본적으로 앱의 메인 스레드에서 실행됩니다. 만약 서비스가 사용자가 같은 앱의 액티비티와 상호작용 중일 동안 무거운 작업 또는
blocking
작업을 한다면 서비스는 액티비티의 성능을 저하시킬 수 있습니다. 앱 성능에 영향을 주는 일을 피하려면 서비스 내에서 새로운 스레드를 생성하세요.
Service
클래스는 모든 서비스들의 기본 클래스, 이 클래스를 확장해 새로운 서비스를 만들 때 서비스가 작업할 때 새로운 스레드를 만들어서 하는 것이 중요함, 서비스는 기본적으로 앱의 메인 스레드를 사용하기 때문에 실행중인 액티비티의 성능을 저하시킬 수 있음
안드로이드 프레임워크는 모든 시작 요청을 한 번에 하나씩 처리하기 위해 워커 스레드를 사용하는 Service
의 하위 클래스인 IntentService
를 제공하지만, 안드로이드 11부터 deprecated
됨. 대신 JobIntentService
를 사용하라고 하는 데 이 클래스도 deprecated
됨.
서비스를 직접 만들기 보다 WorkManager
를 사용하기를 강력히 권장함
class HelloService : Service() {
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
// 스레드로부터 메시지를 받는 Handler
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
// 여기서 실제 작업이 이루어집니다. 예를 들어 파일을 다운로드할 수 있습니다.
// 이 예제에서는 단순히 5초간 대기합니다.
try {
Thread.sleep(5000)
} catch (e: InterruptedException) {
// 인터럽트 상태를 복원합니다.
Thread.currentThread().interrupt()
}
// startId를 사용하여 서비스를 중지합니다.
// 이렇게 하면 다른 작업이 처리 중일 때 서비스가 중지되지 않습니다.
stopSelf(msg.arg1)
}
}
override fun onCreate() {
// 서비스를 실행할 스레드를 시작합니다.
// 서비스는 기본적으로 메인 스레드에서 실행되므로 블로킹을 피하기 위해 별도의 스레드를 만듭니다.
// 또한 UI에 영향을 주지 않도록 백그라운드 우선순위를 사용합니다.
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
// HandlerThread의 Looper를 가져와 Handler에서 사용합니다.
serviceLooper = looper
serviceHandler = ServiceHandler(looper)
}
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
// 각 시작 요청에 대해 메시지를 보내 작업을 시작하고
// 작업이 끝났을 때 어떤 요청을 중지할지 알 수 있도록 startId를 전달합니다.
serviceHandler?.obtainMessage()?.also { msg ->
msg.arg1 = startId
serviceHandler?.sendMessage(msg)
}
// 만약 서비스가 종료되면, 여기서 반환된 후 다시 시작합니다.
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
// 바인딩을 제공하지 않으므로 null을 반환합니다.
return null
}
override fun onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
}
}
위 예제 코드는 onStartCommand()
에서 들어오는 모든 호출을 처리하고, 작업을 백그라운드 스레드에서 실행되는 Handler
에 위임함, 여러 요청을 동시에 처리하고 싶으면 작업을 스레드 풀을 이용해 처리하도록 코드를 변경할 수 있음
onStartCommand()
메소드는 시스템이 서비스를 중단시켰을 때 시스템이 어떻게 서비스를 재개할지를 설명하는 상수를 반환해야 함
START_NOT_STICKY
: onStartCommand()
가 반환된 후 시스템이 서비스를 중단했을 때 전달해야 할 보류된(pending
) 인텐트가 없다면 서비스를 재생성하지 않음, 이는 필요하지 않은 경우 서비스 실행을 방지하고 애플리케이션에서 완료되지 않은 작업을 다시 시작할 수 있는 경우에 가장 안전한 옵션임START_STICKY
: onStartCommand()
가 반환된 후 시스템이 서비스를 중단했을 때 서비스를 재생성하고 onStartCommand()
를 호출함, 이 때 서비스를 시작할 보류된 인텐트가 있지 않다면 빈(null
) 인텐트를 전달함, 이는 명령어를 실행하지 않지만 무기한 실행되고 작업을 기다리는 미디어 플레이어 또는 유사한 서비스에 어울림START_REDELIVER_INTENT
: onStartCommand()
가 반환된 후 시스템이 서비스를 중단했을 때 서비스를 재생성하고 onStartCommand()
를 호출함, 이 때 서비스에 마지막으로 전달되었던 인텐트를 다시 전달함, 그 뒤에 보류된 인텐트가 차례대로 전달됨, 이는 파일 다운로드와 같이 즉시 재개해야 하는 작업을 적극적으로 실행하는 서비스에 적합함startService()
또는 startForegroundService()
에 인텐트를 전달해 서비스를 시작할 수 있음, 이 때 안드로이드 시스템은 서비스의 onStartCommand()
를 호출하고 인텐트를 전달함노트: 앱이 API 레벨 26 이상을 타게팅한다면 시스템은 포그라운드에 있지 않은 앱이 백그라운드 서비스를 생성하거나 사용하는데 제한을 겁니다. 앱이 포그라운드 서비스가 필요하다면 앱에서
startForegroundService()
를 호출해야 합니다. 이 메소드는 백그라운드 서비스를 만들지만 시스템에게 서비스가 포그라운드로 승격해야 한다고 알려줍니다. 서비스가 생성되면startForeground()
메소드를 5초 안에 실행해야 합니다.
HelloService
를 명시적 인텐트를 통해 시작하는 예시startService(Intent(this, HelloService::class.java))
startService()
메소드는 즉시 반환되고, 안드로이드 시스템은 서비스의 onStartCommand()
메소드를 호출함, 서비스가 실행중이 아니라면 시스템은 서비스의 onCreate()
를 먼저 호출하고 onStartCommand()
를 호출함
서비스가 바인딩을 지원하지 않는다면 startService()
에 전달되는 인텐트가 앱 컴포넌트와 통신할 유일한 수단임, 서비스에서 결괏값을 받고싶다면 서비스를 시작할 때 인텐트에 getBroadcast()
를 통해 생성한PendingIntent
를 담아 전달하면 됨 그러면 서비스는 브로드캐스트를 사용해 결과를 전달할 수 있음
서비스를 시작하는 요청이 여러개라면 서비스의 onStartCommand()
는 여러번 호출됨, 하지만 서비스를 중단할 때는 stopSelf()
또는 stopService()
를 한 번만 호출하면 됨
시스템이 서비스를 메모리가 부족할 때가 아니라면 중단하거나 파괴하지 않기 때문에 시작된 서비스는 스스로의 수명주기를 반드시 관리해야 함, 이렇게 파괴되더라도 onStartCommand()
의 반환값에 따라 서비스가 다시 재개될 수 있으므로 서비스는 반드시 stopSelf()
로 스스로 중단하거나, 다른 컴포넌트에서 stopService()
를 호출해 중단해야 함
stopSelf()
또는 stopService()
가 불리면 시스템은 서비스를 최대한 빠르게 파괴함
서비스가 onStartCommand()
에 대한 요청을 동시에 여러번 처리해야 한다면 시작 요청이 처리되었다고 서비스를 중단해서는 안됨, 시작 요청이 끝났을 때 서비스를 중단한다면 다음 요청에 의해 시작된 작업이 같이 중단될 수 있음, 이런 상황을 피하려면 stopSelf(int)
를 사용해 서비스 중지 요청을 가장 최근의 시작 요청과 맞출 수 있음, stopSelf(int)
를 호출할 때 onStartCommand()
에서 전달받은 startId
를 매개변수로 전달하면 가장 최근에 시작 요청을 받았을 때 startId
값과 비교해 맞지 않다면 서비스를 중단하지 않음
주의: 서비스가 작업이 끝났을 때 앱이 확실하게 중단시키게 해 배터리 및 시스템 자원 낭비를 피합시오. 필요하면 다른 컴포넌트에서
stopService()
를 호출해 멈출 수도 있습니다. 서비스가 연결되어있다고 하더라도onStartCommand()
를 통해 시작된 서비스라면 반드시 스스로 중단을 해야 합니다.
연결된 서비스는 앱 컴포넌트가 bindService()
를 호출해 장기적인 연결을 생성하는 것을 허용하는 서비스, 일반적으로 startService()
를 호출해 시작하는 것을 허용하지 않음
연결된 서비스는 같은 앱의 액티비티와 같은 다른 컴포넌트와의 상호작용이 필요하거나, IPC를 통해 앱의 기능을 다른 앱에 노출하고 싶을 때 생성함
연결된 서비스를 생성하려면, 서비스와 통신하기 위한 인터페이스인 IBinder
를 반환하는 onBind()
콜백 메소드를 구현해야 함, 그 후 다른 컴포넌트가 bindService()
를 호출해 콜백 메소드가 반환하는 인터페이스를 가져오고 서비스의 메소드를 호출할 수 있음
이런 서비스는 연결된 컴포넌트에 서비스를 제공하는 동안만 존재하며, 서비스에 연결된 컴포넌트가 없다면 시스템이 파괴함, onStartCommand()
를 통해 시작된 서비스와 같은 방식으로 중단시킬 필요가 없음
여러 클라이언트가 서비스에 동시에 연결될 수 있음, 클라이언트가 서비스와 상호작용이 끝나면 unbindService()
를 호출해 연결 해제를 할 수 있음
연결된 서비스를 구현하는 많은 방식이 있고, 이런 방식들은 구현이 복잡하기 때문에 별도의 문서에서 설명함
서비스가 실행중일 때 스낵바 알림 또는 상태 표시줄 알림을 사용해 사용자에게 알릴 수 있음
스낵바 알림은 현재 화면에 잠시동안 뜨는 메세지, 상태 표시줄 알림은 상태 표시줄에 메세지가 포함된 아이콘을 제공하고, 사용자가 아이콘을 선택하면 작업을 실행할 수 있음(예: 액티비티 시작)
일반적으로 상태 표시줄 알림은 파일 다운로드 처럼 백그라운드 작업이 완료되고 사용자가 작업을 할 수 있을 때 사용하기 좋음
서비스의 수명주기는 액티비티보다 훨씬 간단함, 하지만 서비스는 사용자가 인지하지 못하게 백그라운드에서 실행되기 때문에 서비스가 어떻게 생성되고 파괴되는지 신경쓰는 것이 더 중요할 수 있음
서비스의 수명주기는 서비스의 타입에 따라 다를 수 있음
startService()
로 호출한 서비스는 본인이 직접 stopSelf()
를 호출해 중단하거나, 다른 컴포넌트가 stopService()
를 호출해 중단하기 전까지 계속 실행됨, 이렇게 중단된 서비스는 시스템이 파괴함bindService()
를 호출한 서비스는 IBinder
인터페이스를 통해 클라이언트와 통신하고 클라이언트가 unbindService()
를 호출해 연결을 끝낼 수 있음, 여러 클라이언트가 동시에 서비스에 연결될 수 있고 연결된 모든 클라이언트의 연결이 해제되면 시스템은 서비스를 파괴함, 서비스는 스스로 멈출 필요가 없음하지만 이 두 타입은 완전히 분리되지는 않음, startService()
를 통해 이미 시작된 서비스를 연결할 수 있음, 예를 들어 백그라운드에서 음악을 재생하는 서비스를 어떤 음악을 재생할지 Intent
에 저장하고 startService()
를 호출할 때 전달해 시작하면, 나중에 사용자가 플레이어를 제어하거나 현재 노래에 관한 정보를 가져오려는 경우 액티비티에서 bindService()
를 호출해 서비스에 연결할 수 있음, 이런 경우에 stopService()
나 stopSelf()
는 모든 클라이언트의 연결이 해제되기 전까지 서비스를 중단하지 않음
class ExampleService : Service() {
private var startMode: Int = 0 // 서비스가 종료된 후 어떻게 동작할지 나타냄
private var binder: IBinder? = null // 바인딩을 위한 인터페이스
private var allowRebind: Boolean = false // onRebind 사용 여부
override fun onCreate() {
// 서비스가 생성될 때 호출됨
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// startService()로 서비스가 시작될 때 호출됨
return startMode
}
override fun onBind(intent: Intent): IBinder? {
// bindService()로 클라이언트가 연결할 때 호출됨
return binder
}
override fun onUnbind(intent: Intent): Boolean {
// 모든 클라이언트가 연결 해제될 때 호출됨
return allowRebind
}
override fun onRebind(intent: Intent) {
// onUnbind()가 이미 호출된 후
// 클라이언트가 bindService()를 사용하여 서비스에 연결할 때 호출됨
}
override fun onDestroy() {
// 서비스가 더 이상 사용되지 않고 파괴될 때 호출됨
}
}
노트: 액티비티 수명주기 콜백 메소드와 다르게 슈퍼클래스의 구현을 호출할 필요가 없습니다.
위 그림은 서비스 수명주기를 나타낸 그림, 왼쪽 다이어그램은 startService()
를 통해 시작된 서비스의 수명주기, 오른쪽 다이어그램은 bindService()
를 통해 연결된 서비스의 수명주기, 비록 그림이 시작된 서비스와 연결된 서비스를 구분해 놓았지만, 서비스가 어떻게 시작되었든 클라이언트와 연결될 가능성이 있음을 알고 있어야 함, 자세히 말하면 startService()
로 호출된 서비스가 클라이언트가 bindService()
를 호출해 연결될 수 있다는 뜻임
이런 메소드를 구현해 서비스 수명주기의 두 중첩된 루프를 모니터링할 수 있음
onCreate()
가 호출된 때와 onDestroy()
가 반환하는 때의 사이를 뜻함, 액티비티와 같이 onCreate()
에서 서비스의 초기 셋업을 하고 onDestroy()
에서 남은 자원들을 반환함, 예를 들어 음악 재생 서비스는 onCreate()
에서 음악을 재생하는 스레드를 만들면 onDestroy()
에서 스레드를 중단할 수 있음노트:
onCreate()
와onDestroy()
메소드는startService()
나bindService()
로 생성되었는지와 상관 없이 모든 서비스에서 호출됩니다.
onStartCommand()
또는 onBind()
호출로 시작됨, 각 메소드에는 startService()
또는 bindService()
에서 전달된 Intent
가 전달됨, 시작된 서비스라면 활성 수명은 전체 수명이 끝날때 동시에 끝남, 연결된 서비스라면 onUnbind()
가 반환할 때 활성 수명이 끝남노트: 시작된 서비스는
stopSelf()
나stopService()
가 호출되면 중단되지만, 이에 대한 콜백은 따로 존재하지 않습니다 (onStop()
콜백이 없음). 서비스가 클라이언트에 연결되지 않는 한 시스템은 서비스가 중단될 때 파괴합니다. 이때onDestroy()
가 유일하게 전달되는 콜백입니다.
원문: https://developer.android.com/develop/background-work/services