포그라운드 서비스

포그라운드 서비스는 사용자에게 잘 보이는 작업을 실행합니다.

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

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

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

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

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

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

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" ...>
  <application ...>

    <service
        android:name=".MyMediaPlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="false">
    </service>
  </application>
</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)

자바

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 =
            PermissionChecker.checkSelfPermission(this, Manifest.permission.CAMERA)
    if (cameraPermission != PermissionChecker.PERMISSION_GRANTED) {
        // 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)
        }
        // ...
    }
  }
}

자바

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을 실행하는 기기의 작업 관리자 워크플로를 보여줍니다.

사용자가 작업 관리자에서 앱 옆의 중지 버튼을 누르면 다음 작업이 발생합니다.

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

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

adb shell cmd activity stop-app PACKAGE_NAME

예외

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

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

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

다음과 같은 앱은 포그라운드 서비스를 실행해도 작업 관리자에 전혀 표시되지 않습니다.

사용자가 중지할 수 없음

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

포그라운드 서비스 대신 목적에 맞게 작성된 API 사용

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

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

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

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

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

백그라운드 시작 제한의 예외

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

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

Android 14 (API 수준 34) 이상에서는 사용 중 권한이 필요한 포그라운드 서비스를 시작할 때 유의해야 할 특별한 상황이 있습니다.

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

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

마찬가지로 앱이 백그라운드에 있고 BODY_SENSORS 권한이 필요한 건강 서비스를 만드는 경우 앱에 현재 해당 권한이 없으므로 시스템에서 예외가 발생합니다. (ACTIVITY_RECOGNITION와 같이 다른 권한이 필요한 건강 관리 서비스인 경우에는 적용되지 않습니다.) PermissionChecker.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