서비스 개요

Service는 백그라운드에서 장기 실행 작업을 실행할 수 있는 애플리케이션 구성요소입니다. 사용자 인터페이스를 제공하지 않습니다. 서비스가 시작되면 사용자가 다른 애플리케이션으로 전환한 후에도 한동안 서비스가 계속 실행될 수 있습니다. 또한 구성요소는 서비스에 바인딩되어 서비스와 상호작용하고 프로세스 간 통신 (IPC)을 실행할 수도 있습니다. 예를 들어 서비스는 백그라운드에서 네트워크 트랜잭션을 처리하고, 음악을 재생하거나, 파일 I/O를 실행하거나, 콘텐츠 제공업체와 상호작용할 수 있습니다.

주의: 서비스는 호스팅 프로세스의 기본 스레드에서 실행됩니다. 서비스는 자체 스레드를 생성하지 않으며 별도로 지정하지 않는 한 별도의 프로세스에서 실행되지 않습니다. 애플리케이션 응답 없음 (ANR) 오류를 방지하려면 서비스 내의 별도의 스레드에서 차단 작업을 실행해야 합니다.

서비스 유형

서비스에는 세 가지 유형이 있습니다.

포그라운드

포그라운드 서비스는 사용자가 인지할 수 있는 몇 가지 작업을 실행합니다. 예를 들어 오디오 앱은 포그라운드 서비스를 사용하여 오디오 트랙을 재생합니다. 포그라운드 서비스는 알림을 표시해야 합니다. 포그라운드 서비스는 사용자가 앱과 상호작용하지 않을 때도 계속 실행됩니다.

포그라운드 서비스를 사용할 때는 사용자가 서비스가 실행 중이라는 것을 적극적으로 알 수 있도록 알림을 표시해야 합니다. 이 알림은 서비스를 중지하거나 포그라운드에서 삭제하지 않는 한 닫을 수 없습니다.

앱에서 포그라운드 서비스를 구성하는 방법 자세히 알아보기

참고: WorkManager API는 작업을 예약하는 유연한 방법을 제공하며 필요한 경우 이러한 작업을 포그라운드 서비스로 실행할 수 있습니다. 대부분의 경우 포그라운드 서비스를 직접 사용하는 것보다 WorkManager를 사용하는 것이 좋습니다.

배경
백그라운드 서비스는 사용자가 직접 인식하지 못하는 작업을 실행합니다. 예를 들어 앱에서 서비스를 사용하여 저장소를 압축했다면 일반적으로 백그라운드 서비스입니다.

참고: 앱이 API 수준 26 이상을 타겟팅하는 경우 앱 자체가 포그라운드에 있지 않으면 시스템은 백그라운드 서비스 실행에 제한을 적용합니다. 예를 들어, 대부분의 상황에서는 백그라운드에서 위치 정보에 액세스하면 안 됩니다. 대신 WorkManager를 사용하여 작업을 예약하세요.

바인드
애플리케이션 구성요소가 bindService()를 호출하여 서비스에 바인딩되면 서비스가 바인딩됩니다. 바인드된 서비스는 클라이언트-서버 인터페이스를 제공하여 구성요소가 서비스와 상호작용하고, 요청을 전송하고, 결과를 수신할 수 있게 해주며, 심지어는 프로세스 간 통신 (IPC)을 통해 여러 프로세스에서 이러한 작업을 수행할 수도 있습니다. 바인드된 서비스는 다른 애플리케이션 구성요소가 이 서비스에 바인딩된 경우에만 실행됩니다. 여러 구성요소가 한 번에 서비스에 바인딩될 수 있지만, 모든 구성요소가 바인딩을 해제하면 서비스가 소멸됩니다.

이 문서에서는 시작된 서비스와 바인드된 서비스를 개별적으로 설명하지만 서비스는 두 가지 방식으로 작동할 수 있습니다. 즉, 서비스를 시작하고 (무한히 실행되도록) 바인딩을 허용할 수도 있습니다. 단지 몇 가지 콜백 메서드를 구현할지 여부가 중요합니다. onStartCommand()는 구성요소가 시작할 수 있도록 허용하고 onBind()는 결합을 허용합니다.

서비스가 시작되었는지, 바인딩되었는지 또는 둘 다인지와 관계없이 모든 애플리케이션 구성요소는 Intent로 시작하는 모든 구성요소가 활동을 사용할 수 있는 것과 동일한 방식으로 서비스를 사용할 수 있습니다 (별도의 애플리케이션에서도 사용 가능). 그러나 매니페스트 파일에서 서비스를 비공개로 선언하고 다른 애플리케이션의 액세스를 차단할 수 있습니다. 자세한 내용은 매니페스트에서 서비스 선언 섹션을 참고하세요.

서비스와 스레드 간의 선택

서비스는 단순히 사용자가 애플리케이션과 상호작용하지 않을 때도 백그라운드에서 실행할 수 있는 구성요소이므로 필요한 경우에만 서비스를 만들어야 합니다.

사용자가 애플리케이션과 상호작용하는 동안 기본 스레드 외부에서 작업을 실행해야 하는 경우 대신 다른 애플리케이션 구성요소의 컨텍스트에서 새 스레드를 만들어야 합니다. 예를 들어 활동이 실행되는 동안에만 음악을 재생하려는 경우 onCreate()에서 스레드를 만들고 onStart()에서 실행을 시작한 다음 onStop()에서 중지할 수 있습니다. 또한 기존 Thread 클래스 대신 java.util.concurrent 패키지나 Kotlin 코루틴의 스레드 풀과 실행자를 사용하는 것이 좋습니다. 실행을 백그라운드 스레드로 이동하는 방법에 관한 자세한 내용은 Android의 스레딩 문서를 참고하세요.

서비스를 사용하는 경우 기본적으로 애플리케이션의 기본 스레드에서 계속 실행되므로 서비스가 집약적이거나 차단 작업을 실행하는 경우 서비스 내에 새 스레드를 만들어야 합니다.

기본사항

서비스를 만들려면 Service의 서브클래스를 만들거나 기존 서브클래스 중 하나를 사용해야 합니다. 구현에서는 서비스 수명 주기의 주요 측면을 처리하는 콜백 메서드를 일부 재정의하고 해당하는 경우 구성요소를 서비스에 바인딩할 수 있는 메커니즘을 제공해야 합니다. 다음은 재정의해야 하는 가장 중요한 콜백 메서드입니다.

onStartCommand()
다른 구성요소 (예: 활동)가 서비스 시작을 요청할 때 시스템은 startService()를 호출하여 이 메서드를 호출합니다. 이 메서드가 실행되면 서비스가 시작되고 백그라운드에서 무기한으로 실행될 수 있습니다. 이를 구현하면 서비스가 완료되었을 때 stopSelf() 또는 stopService()를 호출하여 서비스를 중지할 책임이 있습니다. 결합만 제공하려면 이 메서드를 구현하지 않아도 됩니다.
onBind()
시스템은 다른 구성요소가 서비스에 바인딩하려고 할 때 (예: RPC 실행) bindService()를 호출하여 이 메서드를 호출합니다. 이 메서드를 구현할 때 클라이언트가 IBinder를 반환하여 서비스와 통신하는 데 사용하는 인터페이스를 제공해야 합니다. 이 메서드는 항상 구현해야 하지만, 바인딩을 허용하지 않으려면 null을 반환해야 합니다.
onCreate()
시스템은 이 메서드를 호출하여 서비스가 처음 생성될 때 (onStartCommand() 또는 onBind()를 호출하기 전에) 일회성 설정 절차를 실행합니다. 서비스가 이미 실행 중인 경우 이 메서드는 호출되지 않습니다.
onDestroy()
시스템은 서비스가 더 이상 사용되지 않고 소멸될 때 이 메서드를 호출합니다. 서비스는 스레드, 등록된 리스너 또는 수신기와 같은 리소스를 정리하기 위해 이를 구현해야 합니다. 이는 서비스가 수신하는 마지막 호출입니다.

구성요소가 startService()를 호출하여 서비스를 시작하면 (onStartCommand() 호출이 발생함) 서비스는 stopSelf()로 자체적으로 중지되거나 다른 구성요소가 stopService()를 호출하여 서비스를 중지할 때까지 계속 실행됩니다.

구성요소가 bindService()를 호출하여 서비스를 생성하고 onStartCommand()가 호출되지 않은 경우 서비스는 구성요소가 바인딩된 경우에만 실행됩니다. 서비스가 모든 클라이언트에서 바인딩 해제되면 시스템이 이를 소멸시킵니다.

Android 시스템은 메모리가 부족하고 사용자 포커스가 있는 활동의 시스템 리소스를 복구해야 하는 경우에만 서비스를 중지합니다. 서비스가 사용자 포커스가 있는 활동에 바인딩된 경우 종료될 가능성이 작습니다. 서비스가 포그라운드에서 실행되도록 선언되면 종료될 가능성이 거의 없습니다. 서비스가 시작되고 오래 실행되는 경우 시스템은 시간이 지남에 따라 백그라운드 작업 목록에서의 위치를 낮추고 서비스가 종료될 가능성이 매우 높아집니다. 서비스가 시작되면 시스템의 다시 시작을 적절하게 처리하도록 설계해야 합니다. 시스템에서 서비스를 종료하는 경우 리소스를 사용할 수 있게 되는 즉시 서비스가 다시 시작되지만 개발자가 onStartCommand()에서 반환하는 값에 따라 달라집니다. 시스템이 서비스를 소멸시킬 수 있는 경우에 관한 자세한 내용은 프로세스 및 스레딩 문서를 참고하세요.

다음 섹션에서는 startService()bindService() 서비스 메서드를 만드는 방법과 다른 애플리케이션 구성요소에서 이를 사용하는 방법을 보여줍니다.

매니페스트에서 서비스 선언

활동 및 기타 구성요소와 마찬가지로 애플리케이션의 매니페스트 파일에서 모든 서비스를 선언해야 합니다.

서비스를 선언하려면 <service> 요소를 <application> 요소의 하위 요소로 추가합니다. 예를 들면 다음과 같습니다.

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

매니페스트에서 서비스를 선언하는 방법에 관한 자세한 내용은 <service> 요소 참조를 확인하세요.

서비스를 시작하는 데 필요한 권한 및 서비스를 실행해야 하는 프로세스와 같은 속성을 정의하기 위해 <service> 요소에 포함할 수 있는 다른 속성도 있습니다. android:name 속성은 유일한 필수 속성으로, 서비스의 클래스 이름을 지정합니다. 애플리케이션을 게시한 후에는 이 이름을 변경하지 않고 그대로 두어 서비스를 시작하거나 바인딩할 명시적 인텐트에 의존하여 코드가 손상될 위험을 방지합니다 (변경할 수 없는 것 블로그 게시물 읽기).

주의: 앱을 안전하게 보호하려면 Service를 시작할 때 항상 명시적 인텐트를 사용하고 서비스의 인텐트 필터는 선언하지 마세요. 암시적 인텐트를 사용하여 서비스를 시작하면 보안 위험이 발생합니다. 인텐트에 응답하는 서비스를 확신할 수 없고 사용자가 어떤 서비스가 시작되는지 볼 수 없기 때문입니다. Android 5.0 (API 수준 21)부터는 암시적 인텐트로 bindService()를 호출하면 시스템에서 예외가 발생합니다.

android:exported 속성을 포함하고 이 속성을 false로 설정하여 서비스를 내 앱에만 사용할 수 있도록 할 수 있습니다. 이렇게 하면 명시적 인텐트를 사용하는 경우에도 다른 앱이 서비스를 시작하지 못하도록 효과적으로 차단할 수 있습니다.

참고: 사용자는 기기에서 실행 중인 서비스를 확인할 수 있습니다. 알지 못하거나 신뢰할 수 없는 서비스가 표시되면 서비스를 중지할 수 있습니다. 사용자가 실수로 서비스를 중지하지 않도록 하려면 android:description 속성을 앱 매니페스트의 <service> 요소에 추가해야 합니다. 설명에서 서비스의 역할과 서비스가 제공하는 이점을 설명하는 짧은 문장을 작성하세요.

시작된 서비스 생성

시작된 서비스란 다른 구성요소가 startService()를 호출하여 시작하여 서비스의 onStartCommand() 메서드를 호출하는 서비스를 말합니다.

서비스가 시작되면 서비스를 시작한 구성요소와 독립적인 수명 주기를 가지게 됩니다. 서비스는 서비스를 시작한 구성요소가 소멸되었더라도 백그라운드에서 무기한으로 실행될 수 있습니다. 따라서 서비스는 작업이 완료되면 stopSelf()를 호출하여 자체적으로 중지하거나 다른 구성요소가 stopService()를 호출하여 서비스를 중지할 수 있습니다.

활동과 같은 애플리케이션 구성요소는 startService()를 호출하고, 서비스를 지정하고 서비스에서 사용할 모든 데이터를 포함하는 Intent를 전달하여 서비스를 시작할 수 있습니다. 서비스는 onStartCommand() 메서드에서 이 Intent를 수신합니다.

예를 들어 어느 액티비티가 온라인 데이터베이스에 어떤 데이터를 저장해야 한다고 가정해보겠습니다. 활동은 호환 서비스를 시작하고 startService()에 인텐트를 전달하여 저장할 데이터를 제공할 수 있습니다. 서비스는 onStartCommand()에서 인텐트를 수신하고 인터넷에 연결한 다음 데이터베이스 트랜잭션을 실행합니다. 트랜잭션이 완료되면 서비스가 자체적으로 중지되고 소멸됩니다.

주의: 서비스는 기본적으로 자신이 선언된 애플리케이션과 동일한 프로세스에서 실행되며, 애플리케이션의 기본 스레드에서 실행됩니다. 사용자가 동일한 애플리케이션의 활동과 상호작용하는 동안 서비스가 집약적이거나 차단 작업을 실행하는 경우 이 서비스로 인해 활동 성능이 느려집니다. 애플리케이션 성능에 영향을 주지 않으려면 서비스 내에서 새 스레드를 시작하세요.

Service 클래스는 모든 서비스의 기본 클래스입니다. 이 클래스를 확장할 때 서비스가 모든 작업을 완료할 수 있는 새 스레드를 만드는 것이 중요합니다. 서비스는 기본적으로 애플리케이션의 기본 스레드를 사용하므로 애플리케이션이 실행 중인 활동의 성능이 저하될 수 있습니다.

Android 프레임워크는 작업자 스레드를 사용하여 모든 시작 요청을 한 번에 하나씩 처리하는 ServiceIntentService 서브클래스도 제공합니다. 이 클래스는 백그라운드 실행 제한의 도입으로 인해 Android 8 Oreo부터 제대로 작동하지 않으므로 새 앱에는 사용하지 않는 것이 좋습니다. 또한 Android 11부터 지원 중단됩니다. 최신 버전의 Android와 호환되는 IntentService의 대체 요소로 JobIntentService를 사용할 수 있습니다.

다음 섹션에서는 자체 맞춤 서비스를 구현하는 방법을 설명하지만 대부분의 사용 사례에서는 대신 WorkManager를 사용하는 것이 좋습니다. Android의 백그라운드 처리 가이드를 참고하여 요구사항에 맞는 솔루션이 있는지 확인하세요.

서비스 클래스 확장

Service 클래스를 확장하여 각 수신 인텐트를 처리할 수 있습니다. 기본적인 구현은 다음과 같습니다.

Kotlin

class HelloService : Service() {

    private var serviceLooper: Looper? = null
    private var serviceHandler: ServiceHandler? = null

    // Handler that receives messages from the thread
    private inner class ServiceHandler(looper: Looper) : Handler(looper) {

        override fun handleMessage(msg: Message) {
            // Normally we would do some work here, like download a file.
            // For our sample, we just sleep for 5 seconds.
            try {
                Thread.sleep(5000)
            } catch (e: InterruptedException) {
                // Restore interrupt status.
                Thread.currentThread().interrupt()
            }

            // Stop the service using the startId, so that we don't stop
            // the service in the middle of handling another job
            stopSelf(msg.arg1)
        }
    }

    override fun onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
            start()

            // Get the HandlerThread's Looper and use it for our 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()

        // For each start request, send a message to start a job and deliver the
        // start ID so we know which request we're stopping when we finish the job
        serviceHandler?.obtainMessage()?.also { msg ->
            msg.arg1 = startId
            serviceHandler?.sendMessage(msg)
        }

        // If we get killed, after returning from here, restart
        return START_STICKY
    }

    override fun onBind(intent: Intent): IBinder? {
        // We don't provide binding, so return null
        return null
    }

    override fun onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
    }
}

Java

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service. Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block. We also make it
    // background priority so CPU-intensive work doesn't disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

예시 코드는 onStartCommand()의 모든 수신 호출을 처리하고 백그라운드 스레드에서 실행되는 Handler에 작업을 게시합니다. IntentService처럼 작동하며 모든 요청을 차례로 순차적으로 처리합니다. 예를 들어 여러 요청을 동시에 실행하려는 경우 스레드 풀에서 작업을 실행하도록 코드를 변경할 수 있습니다.

onStartCommand() 메서드는 정수를 반환해야 합니다. 정수는 시스템이 서비스를 종료할 때 시스템이 서비스를 계속하는 방법을 설명하는 값입니다. onStartCommand()의 반환 값은 다음 상수 중 하나여야 합니다.

START_NOT_STICKY
onStartCommand()가 반환된 후 시스템이 서비스를 종료하는 경우 전달할 대기 중인 인텐트가 없다면 서비스를 다시 만들지 마세요. 이는 불필요하거나 애플리케이션이 완료되지 않은 작업을 간단히 다시 시작할 수 있는 경우에 서비스가 실행되지 않도록 하는 가장 안전한 옵션입니다.
START_STICKY
onStartCommand()가 반환된 후 시스템이 서비스를 종료하는 경우 서비스를 다시 만들고 onStartCommand()를 호출하되 마지막 인텐트는 다시 전달하지 않습니다. 대신, 서비스를 시작할 대기 중인 인텐트가 없다면 시스템은 null 인텐트로 onStartCommand()를 호출합니다. 이 경우 그러한 인텐트가 전달됩니다. 이는 명령어를 실행하지는 않지만 무기한 실행되고 작업을 대기 중인 미디어 플레이어 (또는 유사 서비스)에 적합합니다.
START_REDELIVER_INTENT
onStartCommand()가 반환된 후 시스템이 서비스를 종료하면 서비스를 다시 만들고 서비스에 전달된 마지막 인텐트로 onStartCommand()를 호출합니다. 모든 보류 인텐트가 차례로 전달됩니다. 이는 즉시 재개되어야 하는 작업을 능동적으로 수행 중인 서비스(예: 파일 다운로드)에 적합합니다.

이러한 반환 값에 대한 자세한 내용은 각 상수에 연결된 참조 문서를 확인하세요.

서비스 시작

IntentstartService() 또는 startForegroundService()에 전달하여 활동 또는 기타 애플리케이션 구성요소에서 서비스를 시작할 수 있습니다. Android 시스템은 서비스의 onStartCommand() 메서드를 호출하여 시작할 서비스를 지정하는 Intent를 전달합니다.

참고: 앱이 API 수준 26 이상을 타겟팅하는 경우 앱 자체가 포그라운드에 있지 않으면 시스템에서 백그라운드 서비스 사용 또는 생성에 제한을 적용합니다. 앱에서 포그라운드 서비스를 만들어야 하는 경우 앱은 startForegroundService()를 호출해야 합니다. 이 메서드는 백그라운드 서비스를 만들지만, 이 메서드는 서비스가 포그라운드로 승격될 것이라고 시스템에 알립니다. 서비스가 생성되면 5초 이내에 startForeground() 메서드를 호출해야 합니다.

예를 들어 활동은 아래와 같이 startService()와 함께 명시적 인텐트를 사용하여 이전 섹션 (HelloService)의 예시 서비스를 시작할 수 있습니다.

Kotlin

startService(Intent(this, HelloService::class.java))

Java

startService(new Intent(this, HelloService.class));

startService() 메서드가 즉시 반환되고 Android 시스템이 서비스의 onStartCommand() 메서드를 호출합니다. 서비스가 아직 실행되고 있지 않으면 시스템은 먼저 onCreate()를 호출한 다음 onStartCommand()를 호출합니다.

서비스가 바인딩도 제공하지 않는 경우 startService()와 함께 전달된 인텐트가 애플리케이션 구성요소와 서비스 간의 유일한 통신 모드입니다. 그러나 서비스가 결과를 다시 전송하도록 하려면 서비스를 시작하는 클라이언트가 브로드캐스트의 PendingIntent(getBroadcast() 사용)를 만들어 서비스를 시작하는 Intent의 서비스에 전달하면 됩니다. 그러면 서비스는 이 브로드캐스트를 사용하여 결과를 전달할 수 있습니다.

서비스 시작을 여러 번 요청하면 서비스의 onStartCommand()에 상응하는 호출이 여러 번 발생합니다. 그러나 서비스를 중지하려면 한 번만 서비스를 중지하도록 요청 (stopSelf() 또는 stopService() 사용)하면 됩니다.

서비스 중지

시작된 서비스는 자신의 수명 주기를 직접 관리해야 합니다. 즉, 시스템은 시스템 메모리를 복구해야 하고 onStartCommand()가 반환된 후에도 서비스가 계속 실행되는 경우를 제외하고 서비스를 중지하거나 소멸하지 않습니다. 서비스는 stopSelf()를 호출하여 자체적으로 중지되어야 합니다. 그렇지 않으면 다른 구성요소가 stopService()를 호출하여 서비스를 중지할 수 있습니다.

stopSelf() 또는 stopService()로 중지를 요청하면 시스템은 가능한 한 빨리 서비스를 소멸합니다.

서비스가 onStartCommand()에 대한 여러 요청을 동시에 처리하는 경우 시작 요청 처리를 완료한 후에는 새 시작 요청을 받았을 수 있으므로 서비스를 중지하면 안 됩니다 (첫 번째 요청이 끝날 때 중지하면 두 번째 요청이 종료될 수 있음). 이 문제를 방지하려면 stopSelf(int)를 사용하여 서비스 중지 요청이 항상 최근 시작 요청을 기반으로 하도록 하면 됩니다. 즉, stopSelf(int)를 호출할 때 중지 요청에 해당하는 시작 요청의 ID (onStartCommand()에 전달된 startId)를 전달합니다. 그런 다음 stopSelf(int)를 호출할 수 있게 되기 전에 서비스가 새 시작 요청을 수신하면 ID가 일치하지 않고 서비스가 중지되지 않습니다.

주의: 시스템 리소스 낭비를 피하고 배터리 전력 소비를 피하려면 애플리케이션이 작업을 완료한 후 서비스를 중지해야 합니다. 필요한 경우 다른 구성요소는 stopService()를 호출하여 서비스를 중지할 수 있습니다. 서비스에 바인딩을 사용 설정해도 서비스가 onStartCommand() 호출을 받으면 항상 직접 서비스를 중지해야 합니다.

서비스 수명 주기에 대한 자세한 내용은 아래의 서비스 수명 주기 관리 섹션을 참고하세요.

바인드된 서비스 생성

바인드된 서비스란 애플리케이션 구성요소가 bindService()를 호출하여 오래 지속되는 연결을 생성함으로써 이 서비스에 바인딩되도록 하는 서비스입니다. 일반적으로 구성요소가 startService()를 호출하여 시작하는 것을 허용하지 않습니다.

애플리케이션의 활동 및 기타 구성요소에서 서비스와 상호작용하거나 애플리케이션 기능 중 일부를 프로세스 간 통신 (IPC)을 통해 다른 애플리케이션에 노출하려는 경우 바인드된 서비스를 생성합니다.

바인드된 서비스를 생성하려면 onBind() 콜백 메서드를 구현하여 서비스와의 통신을 위한 인터페이스를 정의하는 IBinder를 반환합니다. 그러면 다른 애플리케이션 구성요소가 bindService()를 호출하여 인터페이스를 검색하고 서비스의 메서드 호출을 시작할 수 있습니다. 서비스는 바인드된 애플리케이션 구성요소에 서비스를 제공하기 위해서만 존재하므로, 서비스에 바인딩된 구성요소가 없으면 시스템이 서비스를 소멸시킵니다. 바인딩된 서비스는 onStartCommand()를 통해 서비스를 시작할 때와 같은 방식으로 중지할 필요가 없습니다.

바인드된 서비스를 생성하려면 클라이언트가 서비스와 통신하는 방법을 지정하는 인터페이스를 정의해야 합니다. 서비스와 클라이언트 간의 이 인터페이스는 IBinder의 구현이어야 하며 서비스가 onBind() 콜백 메서드에서 반환해야 합니다. 클라이언트는 IBinder를 수신한 후에 해당 인터페이스를 통해 서비스와의 상호작용을 시작할 수 있습니다.

여러 클라이언트가 서비스에 한꺼번에 바인딩될 수 있습니다. 클라이언트가 서비스와의 상호작용을 완료하면 unbindService()를 호출하여 바인딩을 해제합니다. 서비스에 바인딩된 클라이언트가 하나도 없으면 시스템이 해당 서비스를 소멸시킵니다.

바인드된 서비스를 구현하는 방법에는 여러 가지가 있으며 구현은 시작된 서비스보다 더 복잡합니다. 따라서 바인드된 서비스 설명은 바인드된 서비스에 관한 별도의 문서에 나와 있습니다.

사용자에게 알림 전송

서비스가 실행 중일 때 스낵바 알림 또는 상태 표시줄 알림을 사용하여 사용자에게 이벤트를 알릴 수 있습니다.

스낵바 알림은 현재 창의 표면에 잠깐만 사라지기 전에 표시되는 메시지입니다. 상태 표시줄 알림은 사용자가 작업 (예: 활동 시작)을 취하기 위해 선택할 수 있는 메시지가 포함된 아이콘을 상태 표시줄에 제공합니다.

일반적으로 파일 다운로드와 같은 백그라운드 작업이 완료되고 이제 사용자가 조치를 취할 수 있는 경우 상태 표시줄 알림을 사용하는 것이 가장 좋습니다. 사용자가 확장된 뷰에서 알림을 선택하면 알림에서 활동을 시작할 수 있습니다(예: 다운로드한 파일 표시).

서비스 수명 주기 관리

서비스의 수명 주기는 액티비티의 수명 주기보다 훨씬 간단합니다. 하지만 서비스가 생성 및 소멸되는 방식에 세심한 주의를 기울이는 것이 훨씬 더 중요합니다. 서비스가 사용자가 모르게 백그라운드에서 실행될 수 있기 때문입니다.

서비스 수명 주기(생성될 때부터 소멸될 때까지)는 다음 두 경로 중 하나를 따를 수 있습니다.

  • 시작된 서비스

    다른 구성요소가 startService()를 호출하면 서비스가 생성됩니다. 그러면 서비스가 무기한 실행되고 stopSelf()를 호출하여 자체적으로 중지되어야 합니다. 다른 구성요소는 stopService()를 호출하여 서비스를 중지할 수도 있습니다. 서비스가 중단되면 시스템이 이를 소멸시킵니다.

  • 바인딩된 서비스

    다른 구성요소 (클라이언트)가 bindService()를 호출하면 서비스가 생성됩니다. 그러면 클라이언트가 IBinder 인터페이스를 통해 서비스와 통신합니다. 클라이언트는 unbindService()를 호출하여 연결을 종료할 수 있습니다. 여러 클라이언트가 동일한 서비스에 바인딩될 수 있으며, 모든 클라이언트가 바인딩을 해제하면 시스템이 서비스를 소멸시킵니다. 서비스가 자체적으로 중지할 필요는 없습니다.

이 두 경로는 완전히 분리되지 않습니다. 이미 startService()로 시작된 서비스에 바인딩할 수 있습니다. 예를 들어 재생할 음악을 식별하는 IntentstartService()를 호출하여 백그라운드 음악 서비스를 시작할 수 있습니다. 나중에 사용자가 플레이어를 제어하거나 현재 노래에 관한 정보를 가져오려고 할 때 bindService()를 호출하여 활동을 서비스에 바인딩할 수 있습니다. 이 경우 모든 클라이언트가 바인딩을 해제할 때까지 stopService() 또는 stopSelf()는 실제로 서비스를 중지하지 않습니다.

수명 주기 콜백 구현

활동과 마찬가지로 서비스에는 서비스 상태의 변경사항을 모니터링하고 적절한 시점에 작업을 실행하기 위해 구현할 수 있는 수명 주기 콜백 메서드가 있습니다. 다음 스켈레톤 서비스는 각 수명 주기 메서드를 보여줍니다.

Kotlin

class ExampleService : Service() {
    private var startMode: Int = 0             // indicates how to behave if the service is killed
    private var binder: IBinder? = null        // interface for clients that bind
    private var allowRebind: Boolean = false   // indicates whether onRebind should be used

    override fun onCreate() {
        // The service is being created
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // The service is starting, due to a call to startService()
        return startMode
    }

    override fun onBind(intent: Intent): IBinder? {
        // A client is binding to the service with bindService()
        return binder
    }

    override fun onUnbind(intent: Intent): Boolean {
        // All clients have unbound with unbindService()
        return allowRebind
    }

    override fun onRebind(intent: Intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    override fun onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Java

public class ExampleService extends Service {
    int startMode;       // indicates how to behave if the service is killed
    IBinder binder;      // interface for clients that bind
    boolean allowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return startMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return binder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return allowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

참고: 활동 수명 주기 콜백 메서드와 달리 이러한 콜백 메서드의 슈퍼클래스 구현을 호출할 필요는 없습니다.

그림 2. 서비스 수명 주기. 왼쪽의 다이어그램은 startService()로 서비스가 생성된 경우의 수명 주기를 보여주고 오른쪽의 다이어그램은 bindService()로 서비스가 생성된 경우의 수명 주기를 보여줍니다.

그림 2는 서비스에 대한 일반적인 콜백 메서드를 나타낸 것입니다. 이 그림은 startService()에서 만든 서비스와 bindService()에서 만든 서비스를 구분하고 있지만 서비스가 어떻게 시작되든 클라이언트가 서비스에 바인딩되도록 허용할 수 있다는 점에 유의하세요. startService()를 호출하는 클라이언트가 onStartCommand()로 처음에 시작한 서비스는 여전히 onBind() 호출을 수신할 수 있습니다(클라이언트가 bindService()를 호출할 때).

이러한 메서드를 구현하면 서비스 수명 주기의 두 가지 중첩된 루프를 모니터링할 수 있습니다.

  • 서비스의 전체 수명onCreate()가 호출된 시점과 onDestroy()가 반환된 시점 사이에 이루어집니다. 활동과 마찬가지로 서비스는 onCreate()에서 초기 설정을 실행하고 나머지 리소스를 모두 onDestroy()에서 해제합니다. 예를 들어 음악 재생 서비스는 음악이 onCreate()에서 재생되는 스레드를 만든 다음 onDestroy()에서 스레드를 중지할 수 있습니다.

    참고: onCreate() 메서드와 onDestroy() 메서드는 서비스가 startService() 또는 bindService()로 생성되었는지에 관계없이 모든 서비스에 대해 호출됩니다.

  • 서비스의 활성 수명onStartCommand() 또는 onBind() 호출로 시작됩니다. 각 메서드에는 startService() 또는 bindService()에 전달된 Intent가 전달됩니다.

    서비스가 시작되면 전체 수명이 종료되는 동시에 활성 전체 기간이 종료됩니다 (onStartCommand()가 반환된 후에도 서비스는 계속 활성 상태임). 서비스가 바인딩되면 onUnbind()가 반환되면 활성 전체 기간이 종료됩니다.

참고: 시작된 서비스는 stopSelf() 또는 stopService() 호출로 중지되지만 서비스에 관한 각 콜백은 없습니다 (onStop() 콜백은 없음). 서비스가 클라이언트에 바인딩되지 않는 한 시스템은 서비스가 중지되면 이를 소멸시킵니다. 수신된 콜백은 onDestroy()뿐입니다.

결합을 제공하는 서비스 생성에 관한 자세한 내용은 바인드된 서비스 문서를 참고하세요. 이 문서에는 onRebind() 콜백 메서드에 관한 자세한 정보가 바인드된 서비스의 수명 주기 관리 섹션에 관한 내용이 포함되어 있습니다.