안드로이드에는 필수적인 앱 기본 구성 요소로 activity, service, broadcast receiver, content provider 4가지를 가집니다. 이전 글에 이어 service의 started service에 대해 설명하겠습니다.
started service는 다른 component가 startService()를 호출하면, 그 결과로 service의 onStartCommand() 메서드가 호출됩니다. service가 시작되면 이를 시작한 component와는 다른 독립적인 수명 주기를 가지게 됩니다. 해당 service를 시작한 component가 소멸되었더라도 service는 백그라운드에서 계속 실행될 수 있습니다. 따라서 service는 작업이 완료되면 stopSelf()를 호출하여 스스로 중단하거나 다른 component가 stopService()를 호출하여 중단시킵니다.
애플리케이션 component가 service를 시작할 때, startService()를 호출하고 Intent를 전달합니다. 이때 Intent에는 시작할 service를 지정하고 필요한 모든 데이터를 보냅니다. 그럼 이 Intent는 onStartCommand() 메서드에서 받습니다. 예를 들어 어느 activity가 온라인 데이터베이스에서 어떤 데이터를 저장해야 한다고 가정해보겠습니다. activity가 필요한 service를 시작하고, 인텐트를 startService()로 전달하여 service에 저장할 데이터를 보냅니다. service는 이 인텐트를 onStartCommand()에서 수신하고, 인터넷에 연결한 다음, 데이터베이스 트랜잭션을 수행합니다. 트랜잭션이 완료되면 service가 스스로 중단되어 소멸됩니다.
Service 클래스는 모든 service의 기본 클래스입니다. 이 클래스를 extend할 때는 새 스레드를 만드는 것이 중요합니다. service는 기본적으로 애플리케이션의 main 스레드를 사용하는데, 애플리케이션에서 실행 중인 작업에 영향을 주어 수행 속도가 느려질 수 있기 때문입니다. 그렇기에 안드로이드 프레임워크에서는 service의 하위 클래스인 IntentService를 제공했습니다. 이 클래스는 모든 시작 요청에 대해 한 번에 하나씩 처리하는 worker 스레드를 제공합니다. 하지만 백그라운드 실행 제한을 도입한 후, android 8부터 사용을 추천하지 않았으며 android 11부터는 deprecated 되었습니다. 현재는 workManager 사용을 권장하고 있습니다.
❓❓❓
백그라운드 실행 제한이란 백그라운드에서 실행 중인 service는 기기의 리소스를 소비하기 때문에 잠재적으로 나쁜 사용자 경험을 초래할 수 있습니다. 이러한 문제를 완화하기 위해, 시스템은 service에 여러 가지 제한을 적용합니다.
다른 component에서 service를 시작하려면, Intent로 시작할 service를 지정하여 startService() 또는 startForegroundService()에 전달하면 됩니다. 그러면 android 시스템은 service의 onStartCommand() 메서드를 호출하고 여기에 Intent를 전달합니다. activity가 HelloService이라는 service를 시작하려면, startService()로 HelloService라고 명시하는 인텐트를 사용하면 됩니다.
Intent intent = new Intent(this, HelloService.class);
startService(intent);
아직 실행되지 않았던 service라면 먼저 onCreate()를 호출한 다음, onStartCommand()를 호출합니다.
service가 바인딩을 제공하지 않는 경우, startService()와 함께 전달된 인텐트가 component와 service 사이의 유일한 통신 수단입니다. 그러나 service가 결과를 돌려받기를 원하는 경우 service를 시작한 클라이언트가 PendingIngetBroadcast() 메소드를 사용하여 브로드캐스트으로 Intent를 만들고 이를 service에 전달할 수 있습니다. 그러면 service가 이 브로드캐스트으로 전달된 결과를 사용할 수 있게 됩니다.
service를 시작하기 위한 요청을 여러 개 보내면 그에 대응하여 service의 onStartCommand()에 대해 여러 번 호출이 발생합니다. 반면 중단할 때는 stopSelf() 또는 stopService()으로 한 번만 중단을 요청하면 됩니다.
만약 앱이 API 레벨 26 이상을 대상으로 한다면 앱이 포그라운드에 있지 않을 때 시스템에서 백그라운드 service 사용하거나 생성하는 것에 제한을 적용합니다. 즉, 백그라운드 service는 앱이 포그라운드에 있을 때 사용 또는 생성해야 합니다. 사용자가 동시에 실행하는 앱이 많을 경우 시스템에 부하가 걸리는데 이런 경우에 백그라운드에 실행 중인 service가 있으면 더욱 시스템에 부하를 주기 때문입니다.
앱이 포그라운드 service를 생성해야 하는 경우, 해당 앱은 startForegroundService()를 호출해야 합니다. 이 메서드는 백그라운드 service를 생성하지만, 메서드가 시스템에 신호를 보내 service가 자체적으로 포그라운드로 승격될 것이라고 알립니다. service가 생성되면, 앱은 5초 이내에 해당 service의 startForeground() 메서드를 호출해야 합니다. 그럼 새롭게 생성된 service에 대한 알림이 사용자에게 표시됩니다. 앱이 이 시간 한도 내에 startForeground()를 호출하지 않으면 시스템은 service를 중단하고 이 앱을 ANR로 선언합니다.
started service는 자신의 수명 주기를 직접 관리해야 합니다. 즉 시스템이 service를 중단하거나 소멸시키지 않는다는 뜻입니다. 다만 시스템이 부족한 메모리를 확보하기 위해 service를 강제 종료하거나 service가 onStartCommand() 반환 후 계속 실행되는 경우는 예외입니다. service는 stopSelf()를 호출하여 스스로 중지 할 수도 있고 다른 component가 stopService()를 호출하여 이를 중지시킬 수 있습니다. stopSelf() 또는 stopService()로 중단 요청을 보내면 시스템은 가능한 한 빨리 service를 소멸시킵니다.
service에 동시에 여러 onStartCommand() 요청이 있을 경우에는, 처리가 끝났다고 service를 중단하면 안 됩니다. 그 이후 또다른 시작 요청을 새로 받았을 수 있기 때문입니다. 가령 첫 요청이 끝날 때 중단하면 두 번째 요청이 종료될 수 있습니다. 이 문제를 피하려면, stopSelf(int)를 사용하여 service 중단 요청이 항상 가장 최근의 시작 요청을 기준으로 해야 합니다. 다시 말해, stopSelf(int)를 호출할 경우 onStartCommand()에 전달된 마지막 startId을 중단 요청이 대응됩니다. 그런 다음 stopSelf(int)를 호출되기 전에 service가 시작 요청을 새로 수신하면, service의 ID와 최근 startId가 일치하지 않으므로 새로운 servcie는 중단되지 않습니다.
시스템 리소스 낭비를 피하고 배터리 전력 소모를 줄이기 위해서는 service의 작업이 완료되면 애플리케이션에서 service를 중단해야 합니다. 필요한 경우 stopService()를 호출하여 다른 component가 service를 중단하게 합니다. service을 바인딩하더라도, service가 onStartCommand()에 대한 호출을 한 번이라도 받았으면 항상 직접 service를 중단해야 합니다.
모든 service의 기본 클래스입니다. 이 클래스를 확장할 때는 service가 모든 작업을 무사히 완료할 수 있게 새 스레드를 생성하는 것이 중요합니다. service는 기본적으로 애플리케이션의 main 스레드를 사용하기 때문에, 애플리케이션에 실행 중인 activity의 성능을 저하를 막기 위해서입니다. 이 클래스는 시작 요청을 동시에 처리해야 하는 경우에 적합합니다. 다만 대부분의 사용 사례에서 WorkManager 사용을 추천합니다.
service가 멀티스레딩을 수행해야 하는 경우 Service 클래스를 확장하여 각각의 수신 인텐트를 처리하게 할 수 있습니다. 다음은 각 시작 요청에 대해 work 스레드로 작업을 수행하여 한 번에 하나씩만 순차적으로 처리하는 코드 예시입니다.
public class HelloService extends Service {
private Looper serviceLooper;
private ServiceHandler serviceHandler;
//스레드에서 메시지를 받는 핸들러입니다
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// 보통 여기서 파일 다운같은 작업을 합니다.
// 본 예시에서는 단순히 5초 sleep 합니다
try{
Thread.sleep(5000);
}catch(InterruptedException e){
//인터럽트 상태 복원
Thread.currentThread().interrupt();
}
//다른 작업을 처리하는 동안 service를 중지하지 않도록
//startId를 사용하여 service를 중지합니다
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
//service를 실행하는 스레드를 시작합니다.
//service는 일반적으로 프로세스의 main 스레드에서 실행되므로
//방해되지 않게 별도의 스레드를 만듭니다.
//또한 CPU 집약적 작업이 UI를 중단시키지 않도록 백그라운드 우선순위를 설정합니다.
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// HandlerThread의 Looper를 얻어 핸들러에 사용합니다
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 각 시작 요청에 대해 작업을 시작하라는 메시지와 start ID를 전달하면
//작업 완료 시 어떤 요청을 중지해야하는 알 수 있습니다
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
serviceHandler.sendMessage(msg);
//중단되면 이곳으로 리턴되어 재시작합니다
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
//바인딩을 하지 않았기에 null을 반환시킵니다.
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Tost.LENGTH_SHORT).show()
}
}
위 예제코드는 onStartCommand()에 들어오는 모든 호출 처리와 백그라운드 스레드에서 실행되는 Handler 작업을 보여줍니다. 각 호출을 onStartCommand()로 직접 처리하기에 여러 개의 요청을 동시에 수행할 수 있습니다. 물론 위의 코드는 그런 작업을 하지 않지만 원하는 경우, 스레드풀에서 각 요청에 대해 새 스레드를 하나씩 생성한 다음 이전 요청이 끝날 때까지 기다리지 않고 곧바로 실행하면 됩니다.
onStartCommand() 메서드는 반드시 정수를 반환해야 합니다. 이 정수는 시스템이 service를 종료할 경우 service를 어떻게 지속할 지 설명하는 값입니다. onStartCommand()로부터의 반환 값은 반드시 다음 상수 중 하나여야 합니다.
START_STICKY(1, 0x00000001)
시스템이 service를 강제 종료 시킨 후, service를 재생성하여 onStartCommand()를 호출하되, 기존에 onStartCommand()으로 전달된 인텐트는 처리하지 않습니다. 그 대신 시스템이 인텐트 값을 null로 onStartCommand()를 호출합니다. 즉 기존의 인텐트가 null로 초기화 되어 service를 재시작합니다. 단 service를 시작할 때 pending intent를 전달한 경우는 예외로, pending intent가 전달됩니다. 실행할 명령은 없지만, 작업을 기다리고 무한히 실행하면서 명시적으로 시작되고 중지되는 미디어 플레이어 같은 service에 적합합니다.
START_NOT_STICKY(2, 0x00000002)
시스템이 service를 강제 종료 시키면 service를 재생성하지 않습니다. 다만 pending intent가 전달된 경우는 예외입니다. 이는 service가 불필요하게 실행되는 일을 피할 수 있는 가장 유용한 상수이며, 애플리케이션이 완료되지 않은 모든 작업을 단순히 다시 시작할 수 있을 때 유용합니다.
START_REDELIVER_INTENT(3, 0x00000003)
시스템이 service를 강제 종료 시킨 후, service를 다시 생성하고 이 service에 전달된 마지막 인텐트로 onStartCommand()를 호출합니다. 모든 pending intent는 차례로 전달됩니다. 파일 다운로드같이 즉시 재개되어야 하는 작업을 능동적으로 수행 중하는 service 에 적합합니다.
foreground service는 사용자가 인식할 수 있는 작업을 실행합니다. 그만큼 따로 다뤄야 할 내용이 많기 때문에 별도로 foreground service에 대해 자세히 살펴보겠습니다. 이 service는 상태 표시줄 알림을 통해 포그라운드에서 작업을 실행하고 시스템 리소스를 소비하고 있음을 사용자에게 알립니다. foreground service를 사용하는 앱의 예는 다음과 같습니다
앱이 사용자와 직접 상호작용하지 않을 때도 사용자가 인지할 수 있는 작업이면 foreground service를 사용해야 합니다. 작업의 중요도가 낮아 최소 우선순위 알림을 사용하려는 경우에는 대신 background task을 만들어야 합니다. 우선순위 상관 없이 service를 실행하면 활발히 상호작용을 주고받는 앱의 성능을 저해시키기 때문입니다. 대부분의 경우 foreground service 대신 작업을 실행하는 데 사용할 수 있는 전용 플랫폼 또는 Jetpack API가 있습니다. 이러한 API가 있다면 대부분 foreground service 대신 API를 사용하는 것이 좋습니다.
Android 13(API 수준 33)부터 사용자는 기본적으로 foreground service와 연결된 알림을 닫을 수 있습니다. 단순히 사용자가 알림에서 스와이프 동작을 하면 됩니다. 이전에는 foreground service가 중지되거나 포그라운드에서 삭제되지 않는 한 알림이 닫히지 않았습니다. 만약 사용자가 알림을 닫을 수 없도록 하려면 Notification.Builder를 사용하여 알림을 만들 때 setOngoing() 메서드에 true를 전달합니다.
Android 12 (API 수준 31) 이상을 실행하는 기기는 단기 실행 foreground service를 위한 간소화된 환경을 제공합니다. 이러한 기기에서 시스템은 foreground service와 관련된 알림을 표시하기 전에 10초 동안 대기합니다. 예외 사항이 있는데 몇몇 유형의 service는 알림을 항상 즉시 표시하기 때문입니다. 다음 특성 중 하나 이상이 있다면 service가 시작된 직후 연결된 알림을 표시합니다.
Android 13 (API 수준 33) 이상에서 사용자가 알림 권한을 거부하면 작업 관리자 상에는 foreground service와 관련된 알림이 계속 표시되지만 알림 창에는 표시되지 않습니다.
앱의 매니페스트에서 <service> 요소를 사용하여 앱의 각 foreground service를 선언합니다. 각 service에 대해 android:foregroundServiceType 속성을 사용하여 service가 하는 일의 종류를 선언합니다. 예를 들어 앱에서 음악을 재생하는 foreground service를 만드는 경우 다음과 같이 선언할 수 있습니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<service
android:name=".MyMediaPlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="false">
</service>
</manifest>
service에 여러 유형이 적용되는 경우 | 연산자를 사용합니다. 예를 들어 카메라와 마이크를 사용하는 service는 다음과 같이 선언합니다.
android:foregroundServiceType="camera|microphone"
앱에서 타겟팅하는 API 수준에 따라 앱 매니페스트에서 반드시 선언해야 할 foreground service가 있습니다. 하지만 꼭 필요하지 않더라도 모든 foreground service를 선언하고 service 유형을 제공하는 것이 좋습니다.
foreground service를 만드는데 매니페스트에 유형이 선언되지 않은 경우, startForeground() 호출 시 시스템에서 MissingForegroundServiceTypeException이 발생합니다. 하지만 API 29부터 매니페스트에 선언된 유형의 하위 집합에만 액세스하는 경우 다음 코드 스니펫처럼 service 접근을 제한할 수 있습니다.
Notification notification = ...;
Service.startForeground(notification, FOREGROUND_SERVICE_TYPE_LOCATION);
FOREGROUND_SERVICE_TYPE_MANIFEST 유형일 경우 매니페스트 속성에 지정된 모든 플래그를 사용합니다.
Android 9(API 레벨 28) 이상을 대상으로 하고 foreground service를 사용하는 앱은 매니페스트에서 FOREGROUND_SERVICE 권한을 요청해야 합니다. 이 권한은 normal 권한이므로 시스템은 별도의 권한 부여 없이 앱에 자동으로 권한을 부여합니다. 하지만 만약 FOREGROUND_SERVICE 권한 허가 없이 foreground service를 생성하려고 시도하면, 시스템은 SecurityException을 발생시킵니다.
또한 앱이 API 수준 34 이상을 타겟팅하는 경우 foreground service가 실행할 내용에 따른 적절한 권한 유형을 요청해야 합니다. 각 foreground service유형에는 상응하는 권한 유형이 있습니다. 예를 들어 앱이 카메라를 사용하는 foreground service를 실행한다면 FOREGROUND_SERVICE 및 FOREGROUND_SERVICE_CAMERA 권한을 모두 요청해야 합니다. 이는 모두 normal 권한이므로 시스템은 매니페스트에 나열되면 자동으로 권한을 부여합니다. 하지만 만약 필요한 특정 권한을 요청하지 않으면 시스템은 SecurityException을 발생시킵니다.
<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>
🛑🛑🛑
각 service 유형에 따른 특정 기본 요건을 확인하려면 아래의 문서에서 알 수 있습니다.
https://developer.android.com/guide/components/fg-service-types?hl=ko
시스템에 service를 포그라운드로 실행 요청하기 전에, service를 실행하려는 곳에서 service 자체를 시작해야 합니다.
Context context = getApplicationContext();
Intent intent = new Intent(...); // service의 인텐트를 빌드합니다
context.startForegroundService(intent);
일반적으로 service에 있는 onStartCommand()에서 포그라운드로 실행될 수 있게 합니다. 이때 startForeground()를 사용합니다. 이 메서드는 2개 또는 3개의 매개변수를 가집니다.
startForeground (int id, Notification notification)
startForeground (int id, Notification notification, int foregroundServiceType)
id는 알림의 식별자를 의미합니다. 이때 0을 사용하면 안됩니다. 상태 표시줄 알림은 PRIORITY_LOW 이상의 우선순위를 사용해야 합니다. 앱에서 우선순위가 더 낮은 알림을 사용하려고 하면 시스템에서 알림 창에 메시지를 추가하여 앱의 foreground service 사용을 사용자에게 알립니다. foregroundServiceType는 위의 4)에서 언급되었듯이 매니페스트에 선언된 유형의 하위 집합일 때만 사용가능합니다.
service 유형을 더 추가해야 하는 경우 startForeground()를 다시 호출하면 됩니다. 예를 들어 피트니스 앱이 항상 location 정보는 필요하지만 미디어를 재생할 수도 없을 수도 있는 달리기 추적 service를 실행한다고 가정해 보겠습니다. 이런 경우 매니페스트에서 location와 mediaPlayback를 모두 선언해야 합니다. 그럼 위치 정보가 필요할 때 앱은 startForeground()를 호출하여 ACCESS_FINE_LOCATION 권한만 전달 하면 됩니다. 그러다가 사용자가 오디오 재생을 시작하려면 startForeground()를 다시 호출하고 ACCESS_FINE_LOCATION|FOREGROUND_SERVICE_MEDIA_PLAYBACK을 전달합니다. 매니페스트에서 선언하지 않은 foreground service 유형을 전달하면 시스템에서IllegalArgumentException이 발생합니다.
다음은 카메라 foreground service를 실행하는 예입니다.
public class MyCameraService extends Service {
private void startForeground() {
// 포그라운드로 service 시작 전에, 앱에 알맞은 런타인 권한을 가진지 확인
// 여기서는 CAMERA 권한 허가 여부 확인
int cameraPermission =
ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (cameraPermission == PackageManager.PERMISSION_DENIED) {
//카메라 권한이 없으면 포그라운드에서 실행 안됨
//사용자에게 알릴지 앱 UI를 업데이트 할지 고려해야됨
stopSelf();
return;
}
try {
Notification notification =
new NotificationCompat.Builder(this, "CHANNEL_ID")
// service가 실행될 동안 보여질 알림 생성
.build();
int type = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
type = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
}
startForeground( 100, notification, type);
} catch (Exception e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
e instanceof ForegroundServiceStartNotAllowedException
) {
// 백그라운드에서 시작하는 것처럼
// foreground service를 시작할 수 있는 유효 상태가 아님
}
// ...
}
}
//...
}
service를 포그라운드에서 제거하려면 stopForeground()를 호출하면 됩니다. 이 메서드는 상태 표시줄 알림 제거 여부를 나타내는 boolean 값을 취합니다. service가 중단되지는 않습니다. service가 포그라운드에서 실행 중일 때 중단되면 알림도 마찬가지로 제거됩니다.

Android 13(API 레벨 33)부터 SDK 버전에 상관없이 foreground service를 실행하고 있는 앱을 멈추기 위해, 사용자는 알림 드로어에서 작업 흐름을 끝낼 수 있습니다. 이러한 동작을 작업 관리자라 하며 현재 foreground service를 실행 중인 앱 목록을 표시합니다. 이 목록은 활동 중인 앱이라고 부릅니다. 각 앱 옆에는 동작을 멈추는 버튼이 있습니다. 위 사진의 작업 관리자 작업은 Android13에서 실행중입니다. 작업 관리자에 있는 각 앱의 멈춤 버튼을 사용자가 누르게 되면 다음과 같은 일이 벌어집니다.
시스템이 메모리에서 앱을 제거합니다. 그렇기에 foreground service뿐만 아니라 전체 앱이 중지됩니다.
시스템이 앱의 activity 백스택을 제거합니다.
모든 미디어 재생이 중지됩니다.
foreground service 연관된 알람이 제거됩니다.
앱은 기록에 남습니다.
예약된 작업은 예약된 시간에 실행됩니다.
알람은 예약된 시간 또는 알람 창에 울립니다.
사용자가 중지 버튼을 누를 때 시스템은 앱에 콜백을 보내지 않습니다. 앱이 다시 시작할 때 ApplicationExitInfo API의 일부인 REASON_USER_REQUESTED를 통해 원인을 확인할 수 있습니다.
사용자가 앱을 중지하는 동안과 이후에도 앱이 예상대로 작동하는지 테스트하려면 터미널 창에서 다음 ADB 명령을 실행합니다
$adb shell cmd activity stop-app PACKAGE_NAME
시스템은 다음 내용에서 설명하는 특정 유형의 앱에 대해 몇 가지 수준의 예외를 제공합니다. 예외는 프로세스별이 아니라 앱별로 이루어집니다. 시스템이 앱의 한 프로세스를 예외하면 해당 앱의 다른 모든 프로세스도 예외가 됩니다. 제조업체에서 제공한 일부 기본으로 설치된 앱도 예외될 수 있습니다.
작업 관리자에 나타나는 예외
다음 앱은 foreground service를 실행할 수 있지만 작업 관리자에 전혀 표시되지 않습니다.
사용자가 중지하는 것에서의 예외
다음 유형의 앱은 foreground service를 실행하면 작업 관리자에 나타나지만 앱 이름 옆에 사용자가 탭할 수 있는 중지 버튼이 없습니다.
많은 사용 사례에서 foreground service를 통해 할 수 있는 작업을 실행해주는 플랫폼 또는 Jetpack API가 있습니다. 자신의 목적에 맞게 빌드된 API가 있다면 대부분 foreground service 대신 이 API를 사용해야 합니다. 전용 API에는 보통 개발자가 직접 빌드해야 하는 각 사용 사례별 기능을 제공합니다. 예를 들어 Bubbles API는 채팅 풍선 기능을 구현해야 하는 메시지 앱을 위한 복잡한 UI 로직을 처리합니다.
⛔⛔⛔
Bubbles API
https://developer.android.com/guide/topics/ui/bubbles?hl=ko
⛔⛔⛔
포그라운드 서비스 유형 문서에는foreground service대신 사용할 수 있는 좋은 대안이 나열되어 있습니다.
https://developer.android.com/guide/components/fg-service-types?hl=ko
Android 12(API 레벨 31) 이상을 대상으로 하는 앱은 몇 가지 특별한 경우를 제외하고 백그라운드에서 실행되는 동안 foreground service를 시작할 수 없습니다. 만약 예외처리 사항을 처리하지 않았다면 시스템은 ForegroundServiceStartNotAllowedException를 발생시킵니다. 한 앱이 Context.startForgroundService()를 호출하여 다른 앱이 소유한 foreground service를 시작하는 경우, 이러한 제한은 두 앱 모두 Android 12 이상을 대상으로 하는 경우에만 적용됩니다.
다음과 같은 경우에는 앱이 백그라운드에서 실행되는 동안에도 foreground service를 시작할 수 있습니다.
⛔⛔⛔
더 자세한 사항은 공식문서 참고해주세요.
https://developer.android.com/develop/background-work/services/foreground-services?hl=ko#background-start-restriction-exemptions
Android 14(API 수준 34) 이상에서는 사용 중에 권한이 필요한 foreground service를 시작하는 경우 주의해야 하는 특수한 상황이 있습니다. 앱 사용 중에 권한이 필요한 foreground service를 실행하려는 경우, 앱이 백그라운드 시작 제한의 예외 중 하나라도 속해도 백그라운드에 있는 동안에는 serivce를 만들 수 없습니다. 앱이 Android 14 이상을 타겟팅할 때, 운영체제는 개발자가 foreground service를 만들면 앱은 이 service 유형에 적합한 모든 권한이 있는지 확인합니다. 예를 들어 microphone 유형의 foreground service를 생성하면 운영체제는 현재 앱에 RECORD_AUDIO 권한이 있는지 확인합니다. 이 권한이 없으면 시스템에서 SecurityException이 발생합니다.
이는 잠재적인 문제가 발생할 수 있습니다. 앱 사용 중에 권한이 필요하는 경우 포그라운드에 있는 동안에만 해당 권한을 갖습니다. 즉, 앱이 백그라운드에 있고 카메라, 위치 또는 마이크 유형의 foreground service를 만들려고 하면 시스템은 앱에 현재 필요한 권한이 없다고 판단하여 SecurityException을 발생시킵니다. 예를 들어 앱이 백그라운드에 있고 BODY_SENSORS_BACKGROUND 권한이 필요한 건강 service를 만드는 경우, 앱에는 현재 해당 권한이 없으며 시스템에서 예외가 발생합니다. ACTIVITY_RECOGNITION 권한은 해당되지 않습니다.
ContextCompat.checkSelfPermission()를 호출해도 이 문제를 예방할 수 없습니다. 앱에 사용 해야 할 권한이 있는지 확인하기 위해 checkSelfPermission()를 호출하면, 앱이 백그라운드에 있더라도 PERMISSION_GRANTED를 반환합니다. PERMISSION_GRANTED는 '앱이 사용되는 동안 앱에 이 권한이 있습니다.'라는 의미입니다. 즉, checkSelfPermission()를 호출하여도 권한이 있다고 반환하기 때문에 잘못된 판단을 야기합니다.
Android 14 미만 버전에서 앱이 백그라운드에 있을 때 사용 중 권한이 필요한 foreground service를 만들려고 하면, 시스템에서는 service를 만들 수 있지만 service는 필요한 리소스에 액세스할 수 없으며 이를 사용하려고 하면 예외가 발생합니다. Android 14 이상에서는 즉시 예외가 발생합니다. 이러한 이유로 제한 사항 예외에 한 개라도 속하지 않는 다면, 앱에 표시되는 activity가 있는 동안 Context.startForegroundService() 또는 Context.bindService()를 호출해야 합니다.
경우에 따라 앱이 백그라운드에서 실행되는 동안 foreground service가 시작되더라도 앱이 포그라운드으로 전환되어 실행되는 동안(사용 중)에는 위치, 카메라 및 마이크 정보에 액세스할 수 있습니다. 이런 상황에서 service가 location 유형을 선언하고 ACCESS_BACKGROUT_LOCATION 권한을 가진 앱에서 시작하는 경우, 이 service는 앱이 백그라운드에서 실행되는 경우에도 항상 위치 정보에 액세스할 수 있습니다. 다음 내용은 위 상황의 예시를 말합니다.
앱을 테스트할 때 자신의 foreground service를 시작합니다. started service가 위치, 마이크 및 카메라에 대한 액세스를 제한한 경우 다음 메시지가 Logcat에 나타납니다.
Foreground service started from background can not have \
location/camera/microphone access: service SERVICE_NAME
이전 SERVICE 포스트
<service_1>
다음 SERVICE 포스트
<service_3>
<service 예제>