포그라운드 서비스

포그라운드 서비스는 사용자가 인지할 수 있는 작업을 실행합니다.

포그라운드 서비스는 상태 표시줄 알림을 표시하여 앱이 포그라운드에서 작업을 실행하고 시스템 리소스를 소비하고 있음을 사용자에게 알립니다.

포그라운드 서비스를 사용하는 앱의 예는 다음과 같습니다.

  • 포그라운드 서비스에서 음악을 재생하는 음악 플레이어 앱 알림에 현재 재생 중인 노래가 표시될 수 있습니다.
  • 사용자로부터 권한을 받은 후 포그라운드 서비스에서 사용자의 달리기를 기록하는 피트니스 앱 알림에는 사용자가 현재 피트니스 세션 중에 이동한 거리가 표시될 수 있습니다.

사용자가 앱과 직접 상호작용하지 않을 때도 앱에서 사용자가 인지할 수 있는 작업을 실행해야 할 때만 포그라운드 서비스를 사용합니다. 작업의 중요도가 낮아 최소 우선순위 알림을 사용하려는 경우 대신 백그라운드 작업을 만듭니다.

이 문서에서는 포그라운드 서비스를 사용하는 데 필요한 권한과 포그라운드 서비스를 시작하고 백그라운드에서 삭제하는 방법을 설명합니다. 또한 특정 사용 사례를 포그라운드 서비스 유형과 연결하는 방법 및 백그라운드에서 실행 중인 앱에서 포그라운드 서비스를 시작할 때 적용되는 액세스 제한에 관해 설명합니다.

사용자가 기본적으로 알림을 닫을 수 있음

Android 13 (API 수준 33)부터 사용자는 기본적으로 포그라운드 서비스와 연결된 알림을 닫을 수 있습니다. 이렇게 하려면 사용자가 알림에서 스와이프 동작을 실행합니다. 기본적으로 알림은 포그라운드 서비스가 중지되거나 포그라운드에서 삭제되지 않는 한 닫히지 않습니다.

사용자가 알림을 닫을 수 없도록 하려면 Notification.Builder를 사용하여 알림을 만들 때 truesetOngoing() 메서드에 전달합니다.

즉시 알림을 표시하는 서비스

포그라운드 서비스에 다음 특성 중 하나 이상이 있다면 시스템은 Android 12 이상을 실행하는 기기에서도 서비스가 시작된 직후에 관련 알림을 표시합니다.

Android 13 (API 수준 33) 이상에서 사용자가 알림 권한을 거부하면 작업 관리자에는 포그라운드 서비스와 관련된 알림이 계속 표시되지만 알림 창에는 표시되지 않습니다.

매니페스트에서 포그라운드 서비스 선언

앱의 매니페스트에서 <service> 요소를 사용하여 앱의 각 포그라운드 서비스를 선언합니다. 각 서비스에 대해 android:foregroundServiceType 속성을 사용하여 서비스가 실행하는 작업 유형을 선언합니다.

예를 들어 앱에서 음악을 재생하는 포그라운드 서비스를 만드는 경우 서비스를 다음과 같이 선언할 수 있습니다.

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

    <service
        android:name=".MyMediaPlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="false">
    </service>
</manifest>

서비스에 여러 유형이 적용되는 경우 | 연산자로 유형을 구분하세요. 예를 들어 카메라와 마이크를 사용하는 서비스는 다음과 같이 선언합니다.

android:foregroundServiceType="camera|microphone"

포그라운드 서비스 권한 요청

Android 9 (API 수준 28) 이상을 타겟팅하고 포그라운드 서비스를 사용하는 앱은 다음 코드 스니펫과 같이 앱 매니페스트에서 FOREGROUND_SERVICE를 요청해야 합니다. 이는 정상적인 권한이므로 시스템은 요청 앱에 자동으로 권한을 부여합니다.

또한 앱이 API 수준 34 이상을 타겟팅하는 경우 포그라운드 서비스가 실행할 작업 유형에 적절한 권한 유형을 요청해야 합니다. 각 포그라운드 서비스 유형에는 상응하는 권한 유형이 있습니다. 예를 들어 앱이 카메라를 사용하는 포그라운드 서비스를 실행하면 FOREGROUND_SERVICEFOREGROUND_SERVICE_CAMERA 권한을 모두 요청해야 합니다. 이러한 권한은 모두 일반 권한이므로 시스템은 매니페스트에 나열되면 자동으로 권한을 부여합니다.

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

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>

    <application ...>
        ...
    </application>
</manifest>

포그라운드 서비스 기본 요건

Android 14 (API 수준 34)부터 포그라운드 서비스를 실행하면 시스템에서 서비스 유형에 따라 특정 기본 요건을 확인합니다. 예를 들어 location 유형의 포그라운드 서비스를 실행하려고 하면 시스템은 앱에 이미 ACCESS_COARSE_LOCATION 또는 ACCESS_FINE_LOCATION 권한이 있는지 확인합니다. 그렇지 않으면 시스템에서 SecurityException이 발생합니다.

따라서 포그라운드 서비스를 시작하기 전에 필수 기본 요건을 충족하는지 확인해야 합니다. 포그라운드 서비스 유형 문서에는 각 포그라운드 서비스 유형에 필요한 기본 요건이 나열되어 있습니다.

포그라운드 서비스 시작

시스템에 서비스를 포그라운드 서비스로 실행하도록 요청하기 전에 서비스 자체를 시작합니다.

Kotlin

val intent = Intent(...) // Build the intent for the service
context.startForegroundService(intent)

Java

Context context = getApplicationContext();
Intent intent = new Intent(...); // Build the intent for the service
context.startForegroundService(intent);

서비스 내에서(일반적으로 onStartCommand()에서) 서비스가 포그라운드에서 실행되도록 요청할 수 있습니다. 이렇게 하려면 ServiceCompat.startForeground()(androidx-core 1.12 이상에서 사용 가능)를 호출합니다. 이 메서드는 다음 매개변수를 사용합니다.

이러한 유형은 특정 사용 사례에 따라 매니페스트에 선언된 유형의 하위 집합일 수 있습니다. 그런 다음 서비스 유형을 더 추가해야 하는 경우 startForeground()를 다시 호출하면 됩니다.

예를 들어 피트니스 앱에서 항상 location 정보가 필요하지만 미디어를 재생할 수도 있고 재생하지 않을 수도 있는 달리기 추적기 서비스를 실행한다고 가정해 보겠습니다. 매니페스트에서 locationmediaPlayback를 모두 선언해야 합니다. 사용자가 달리기를 시작하고 위치만 추적하고 싶다면 앱은 startForeground()를 호출하고 ACCESS_FINE_LOCATION 권한만 전달해야 합니다. 그런 다음, 사용자가 오디오 재생을 시작하려면 startForeground()를 다시 호출하고 모든 포그라운드 서비스 유형의 비트 조합 (이 경우에는 ACCESS_FINE_LOCATION|FOREGROUND_SERVICE_MEDIA_PLAYBACK)을 전달합니다.

다음은 카메라 포그라운드 서비스를 실행하는 예입니다.

Kotlin

class MyCameraService: Service() {

  private fun startForeground() {
    // Before starting the service as foreground check that the app has the
    // appropriate runtime permissions. In this case, verify that the user has
    // granted the CAMERA permission.
    val cameraPermission =
            ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
    if (cameraPermission == PackageManager.PERMISSION_DENIED) {
        // Without camera permissions the service cannot run in the foreground
        // Consider informing user or updating your app UI if visible.
        stopSelf()
        return
    }

    try {
        val notification = NotificationCompat.Builder(this, "CHANNEL_ID")
            // Create the notification to display while the service is running
            .build()
        ServiceCompat.startForeground(
            /* service = */ this,
            /* id = */ 100, // Cannot be 0
            /* notification = */ notification,
            /* foregroundServiceType = */
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
            } else {
                0
            },
        )
    } catch (e: Exception) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                && e is ForegroundServiceStartNotAllowedException) {
            // App not in a valid state to start foreground service
            // (e.g. started from bg)
        }
        // ...
    }
  }
}

Java

public class MyCameraService extends Service {

    private void startForeground() {
        // Before starting the service as foreground check that the app has the
        // appropriate runtime permissions. In this case, verify that the user
        // has granted the CAMERA permission.
        int cameraPermission =
            ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
        if (cameraPermission == PackageManager.PERMISSION_DENIED) {
            // Without camera permissions the service cannot run in the
            // foreground. Consider informing user or updating your app UI if
            // visible.
            stopSelf();
            return;
        }

        try {
            Notification notification =
                new NotificationCompat.Builder(this, "CHANNEL_ID")
                    // Create the notification to display while the service
                    // is running
                    .build();
            int type = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                type = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
            }
            ServiceCompat.startForeground(
                    /* service = */ this,
                    /* id = */ 100, // Cannot be 0
                    /* notification = */ notification,
                    /* foregroundServiceType = */ type
            );
        } catch (Exception e) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
                    e instanceof ForegroundServiceStartNotAllowedException
            ) {
                // App not in a valid state to start foreground service
                // (e.g started from bg)
            }
            // ...
        }
    }

    //...
}

포그라운드에서 서비스 삭제

포그라운드에서 서비스를 삭제하려면 stopForeground()를 호출합니다. 이 메서드는 상태 표시줄 알림도 삭제할지 여부를 나타내는 불리언을 사용합니다. 서비스는 계속 실행됩니다.

포그라운드에서 실행되는 서비스를 중지하면 알림이 삭제됩니다.

사용자가 시작한 포그라운드 서비스 실행 앱 중지 처리

알림 창 하단에는 현재 백그라운드에서 실행 중인 앱의 수를 나타내는 버튼이 있습니다. 이 버튼을 누르면 여러 앱의 이름이 나열된 대화상자가 나타납니다. 중지 버튼은 각 앱의 오른쪽에 있습니다.
그림 1. Android 13 이상을 실행하는 기기의 작업 관리자 워크플로

Android 13 (API 수준 33)부터 사용자는 앱의 타겟 SDK 버전과 관계없이 알림 창에서 워크플로를 완료하여 진행 중인 포그라운드 서비스가 있는 앱을 중지할 수 있습니다. 작업 관리자라고 하는 이 어포던스는 현재 포그라운드 서비스를 실행 중인 앱 목록을 표시합니다.

이 목록에는 활성 앱이라는 라벨이 지정되어 있습니다. 각 앱의 옆에는 중지 버튼이 있습니다. 그림 1은 Android 13을 실행하는 기기의 작업 관리자 워크플로를 보여줍니다.

사용자가 작업 관리자에서 앱 옆의 Stop 버튼을 누르면 다음 작업이 실행됩니다.

  • 시스템이 메모리에서 앱을 제거합니다. 따라서 실행 중인 포그라운드 서비스뿐만 아니라 전체 앱이 중지됩니다.
  • 시스템에서 앱의 활동 백 스택을 제거합니다.
  • 모든 미디어 재생이 중지됩니다.
  • 포그라운드 서비스와 연결된 알림이 삭제됩니다.
  • 앱은 기록에 남아 있습니다.
  • 예약된 작업은 예약된 시간에 실행됩니다.
  • 예약된 시간이나 시간에 알람이 울립니다.

사용자가 앱을 중지하는 동안과 중지한 후에 앱이 예상대로 작동하는지 테스트하려면 터미널 창에서 다음 ADB 명령어를 실행하세요.

adb shell cmd activity stop-app PACKAGE_NAME

예외

시스템은 이어지는 섹션에서 설명하는 특정 유형의 앱에 대해 여러 수준의 예외를 제공합니다.

예외는 프로세스가 아닌 앱별로 적용됩니다. 시스템이 하나의 앱에서 하나의 프로세스에 예외를 제공한 경우 이 앱의 다른 모든 프로세스에도 예외가 제공됩니다.

작업 관리자에 전혀 표시되지 않음

다음 앱은 포그라운드 서비스를 실행할 수 있지만 작업 관리자에 아예 표시되지 않습니다.

사용자가 중지할 수 없음

다음 유형의 앱이 포그라운드 서비스를 실행하면 작업 관리자에 표시되지만 사용자가 탭할 수 있는 앱 이름 옆에 Stop 버튼이 없습니다.

포그라운드 서비스 대신 특별히 빌드된 API 사용

많은 사용 사례의 경우 포그라운드 서비스를 사용할 수도 있는 작업을 실행하는 데 사용할 수 있는 플랫폼 또는 Jetpack API가 있습니다. 적합한 목적에 맞게 빌드된 API가 있다면 거의 항상 포그라운드 서비스 대신 이 API를 사용해야 합니다. 특별히 개발된 API는 자체적으로 빌드해야 하는 추가 사용 사례별 기능을 제공하는 경우가 많습니다. 예를 들어 Bubbles API는 채팅 풍선 기능을 구현해야 하는 메시지 앱의 복잡한 UI 로직을 처리합니다.

포그라운드 서비스 유형 문서에 포그라운드 서비스 대신 사용할 수 있는 좋은 대안이 나와 있습니다.

백그라운드에서 포그라운드 서비스를 시작할 때 적용되는 제한사항

Android 12 이상을 타겟팅하는 앱은 앱이 백그라운드에서 실행되는 동안 포그라운드 서비스를 시작할 수 없습니다(몇 가지 특별한 사례 제외). 앱이 백그라운드에서 실행되는 동안 포그라운드 서비스를 시작하려고 하지만 포그라운드 서비스가 예외 사례 중 하나를 충족하지 못하면 시스템에서 ForegroundServiceStartNotAllowedException이 발생합니다.

또한 앱에서 사용 중 권한 (예: 생체 신호 센서, 카메라, 마이크 또는 위치 정보 액세스 권한)이 필요한 포그라운드 서비스를 실행하려는 경우 앱이 백그라운드 시작 제한의 예외 중 하나에 속하더라도 앱이 백그라운드에 있는 동안에는 서비스를 만들 수 없습니다. 그 이유는 사용 중 권한이 필요한 포그라운드 서비스 시작 제한사항 섹션에 설명되어 있습니다.

백그라운드 시작 제한 예외

다음 상황에서는 앱이 백그라운드에서 실행되는 동안에도 포그라운드 서비스를 시작할 수 있습니다.

사용 중 권한이 필요한 포그라운드 서비스 시작에 관한 제한사항

Android 14 (API 수준 34) 이상에서는 사용 중 권한이 필요한 포그라운드 서비스를 시작하는 경우 알아야 할 특수한 상황이 있습니다.

앱이 Android 14 이상을 타겟팅하는 경우 운영체제는 개발자가 포그라운드 서비스를 만들 때 이를 확인하여 앱에 해당 서비스 유형에 적합한 모든 권한이 있는지 확인합니다. 예를 들어 마이크 유형의 포그라운드 서비스를 만들 때 운영체제는 앱에 현재 RECORD_AUDIO 권한이 있는지 확인합니다. 이 권한이 없으면 시스템에서 SecurityException이 발생합니다.

사용 중 권한의 경우 이로 인해 잠재적인 문제가 발생할 수 있습니다. 앱에 사용 중 권한이 있는 경우 포그라운드에 있는 동안에만 해당 권한을 가집니다. 즉, 앱이 백그라운드에 있고 카메라, 위치 또는 마이크 유형의 포그라운드 서비스를 만들려고 하면 시스템은 앱에 현재 필요한 권한이 없는 것을 확인하고 SecurityException을 발생시킵니다.

마찬가지로 앱이 백그라운드에 있고 BODY_SENSORS_BACKGROUND 권한이 필요한 상태 서비스를 만드는 경우 앱에는 현재 이 권한이 없으므로 시스템에서 예외가 발생합니다. ACTIVITY_RECOGNITION와 같이 다른 권한이 필요한 건강 서비스인 경우에는 적용되지 않습니다. ContextCompat.checkSelfPermission()를 호출해도 이 문제가 방지되지 않습니다. 앱에 사용 중 권한이 있고 앱이 checkSelfPermission()를 호출하여 이 권한이 있는지 확인하는 경우, 메서드는 앱이 백그라운드에 있더라도 PERMISSION_GRANTED를 반환합니다. 메서드에서 PERMISSION_GRANTED를 반환하면 '앱이 사용되는 동안 앱에 이 권한이 있습니다.'라는 메시지가 표시됩니다.

이러한 이유로 포그라운드 서비스에 사용 중 권한이 필요한 경우 서비스가 정의된 예외 중 하나에 속하는 경우를 제외하고 앱에 표시되는 활동이 있는 동안 Context.startForegroundService() 또는 Context.bindService()를 호출해야 합니다.

사용 중 권한 제한 예외

경우에 따라 앱이 백그라운드에서 실행되는 동안 포그라운드 서비스가 시작되더라도 앱이 포그라운드에서 실행되는 동안('사용 중') 위치, 카메라, 마이크 정보에 계속 액세스할 수 있습니다.

이와 동일한 상황에서 서비스가 location포그라운드 서비스 유형을 선언하고 ACCESS_BACKGROUND_LOCATION 권한이 있는 앱에서 시작하면 이 서비스는 앱이 백그라운드에서 실행되더라도 항상 위치 정보에 액세스할 수 있습니다.

다음 목록에는 이러한 상황이 포함되어 있습니다.

  • 시스템 구성요소가 서비스를 시작합니다.
  • 서비스는 앱 위젯과 상호작용하면서 시작됩니다.
  • 서비스는 알림과 상호작용하면서 시작됩니다.
  • 서비스는 표시된 다른 앱에서 전송된 PendingIntent로 시작됩니다.
  • 서비스는 기기 소유자 모드에서 실행되는 기기 정책 컨트롤러인 앱에 의해 시작됩니다.
  • 서비스는 VoiceInteractionService를 제공하는 앱에서 시작합니다.
  • 서비스가 START_ACTIVITIES_FROM_BACKGROUND 독점 권한이 있는 앱에 의해 시작됩니다.
앱에서 영향을 받는 서비스 확인

앱을 테스트할 때 포그라운드 서비스를 시작합니다. 시작된 서비스에 위치, 마이크 및 카메라 액세스 제한이 적용된 경우 다음 메시지가 Logcat에 표시됩니다.

Foreground service started from background can not have \
location/camera/microphone access: service SERVICE_NAME