포그라운드 서비스

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

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

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

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

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

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

사용자는 기본적으로 알림을 닫을 수 있습니다.

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)

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 =
            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)
        }
        // ...
    }
  }
}

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

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

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

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

adb shell cmd activity stop-app PACKAGE_NAME

예외

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

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

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

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

사용자가 중지할 수 없음

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

포그라운드 서비스 대신 목적에 맞게 빌드된 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와 같이 다른 권한이 필요한 건강 서비스인 경우에는 적용되지 않습니다.) 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