안드로이드 Oreo(8.0) 버전에서 등장한 백그라운드 실행 제한을 확인해보도록 하겠다. 안드로이드에서 백그라운드 실행은 상용 서비스에서 많이 사용되는 부분이기도 하고 꼭 알아야 하는 부분이라서 공부하면서 정리하려고 한다.

1. Oreo 버전 백그라운드 제한 개요

앱이 백그라운드에서 실행될 때마다 디바이스의 리소스(예:RAM, 배터리)를 사용한다. 이는 사용자들에게 좋지 못한 경험을 제공한다. 예를 들어 백그라운드 작업으로 인해 디바이스의 배터리 수명이 저하되거나 비디오 시청, 게임, 카메라 사용과 같은 사용자의 디바이스 성능 저하가 발생할 수 있다.

동시에 실행되는 앱이 많은 수록 시스템에 많은 부하가 걸린다. 추가적인 앱이나 서비스가 백그라운드에서 실행 중이면 시스템에 추가적인 부하가 걸리고 사용자 환경이 나빠질 수 있다. 이런 문제가 발생할 가능성을 줄이기 위해 Android Oreo 버전에서는 사용자가 앱과 직접적으로 상호작용하지 않을 때 이 앱이 수행할 수 있는 작업을 제한한다. 두 가지 방식으로 제한된다.(여기서는 백그라운드 서비스 제한만 살펴보도록 하겠다.)

  1. 백그라운드 서비스 제한 : 앱이 유휴 상태인 경우 백그라운드 서비스의 사용이 제한된다. 이 기능은 사용자에게 잘 보이는 포그라운드 서비스에는 적용되지 않는다.

  2. 브로드캐스트 제한 : 제한된 예외의 경우, 앱이 암시적 브로드캐스트에 등록하기 위해 자체 매니페스트를 사용할 수 없다. 그렇지만 앱이 런타임에 브로드캐스트에 등록할 수 있으며, 특정 앱을 대상으로 하는 명시적 브로드캐스트에 등록하기 위해 매니페스트를 사용할 수 있다.

  • 기본적으로 이와 같은 제한은 Oreo 버전을 대상으로 하는 앱에만 적용된다. 하지만, 안드로이드 개발자라면 모든 버전을 고려해서 개발해야 하기 때문에 피할 수 없는 부분임이 분명하다. 그렇기 때문에 반드시 알고 넘어가야 한다.

  • 추가적으로 이전에 살펴본 Service는 백그라운드에서 동작하는 컴포넌트이다. 하지만, Oreo 처럼 백그라운드 작업을 제한하게 된다면 우리가 원하는 작업을 수월하게 진행할 수 없다. 이 부분을 어떻게 해결할 수 있는지 고민해야 할 필요가 있다.

대부분의 경우, 앱은 JobScheduler 작업을 사용해 이 제한을 해결할 수 있다. JobScheduler를 통해 앱이 실행되지 않을 때 작업을 수행하면서도, 사용자 환경에 영향을 미치지 않는 방식으로 이러한 작업을 예약할 수 있는 여지를 제공할 수 있다. 하지만, JobScheduler 역시 제한이 걸리는 점이 존재한다. 이 부분은 WorkManager 부분에서 공부하면서 살펴보자.

2. 백그라운드 실행 변화

앞선 포스팅에서 확인한 내용을 한번 더 정리하고 넘어가려 한다. 그리고 위에서 설명한 것처럼 사용자의 디바이스 배터리 수명을 개선하고 더 좋은 사용자 경험을 제공하기 위해 안드로이드는 몇가지 백그라운드 정책들을 변화시켜왔다.

  • 도즈모드와 앱 대기상태(Doze And App Standby) : 화면이 꺼지고 충전 상태가 아니며 대기 상태일 때 애플리케이션의 행동을 제한한다.
  • 백그라운드 상태에서 위치 서비스 제약 : 백그라운드 상태에 있는 앱이 사용자의 현재위치를 검색할 수 있는 빈도를 제한한다.
  • 백그라운드 서비스 제약 : 보이지 않는 CPU/Network 사용과 실행중인 백그라운드 서비스들을 제한한다.
  • 가장 최근에는 사용자가 사용하지 않는 앱에서 사용할 수 있는 기기 리소스를 제한할 수 있는 앱 대기 버킷(App Standby Buckets)과 앱이 좋지 않는 행동을 보이는 경우 백그라운드에서 시스템 리소스에 대한 앱의 접근을 제한하도록 사용자에게 경고하는 앱 제한(App Restrictions)배터리 절약 개선(Battery Saver improvements) 정책이 추가되었다.

3. Oreo 버전 백그라운드 실행 제한.

위에서 언급한 것처럼 백그라운드에서 실행 중인 서비스가 디바이스의 리소스를 사용할 수 있으며, 그 결과로 사용자 경험이 악화될 가능성이 있다. 이러한 문제를 줄이기 위해 Oreo 버전부터 시스템은 여러가지 제한을 서비스에 적용한다.

먼저, 이를 이해하기 전에 애플리케이션이 Foreground인지 Background인지 명확하게 파악해야 한다. 아래와 같은 경우 애플리케이션이 Foreground에 있는 것으로 간주한다.

  • 액티비티가 시작되거나 일지 중지되거나 상관없이 액티비티가 가시적일 경우.
  • Foreground Service가 있는 경우.
  • Foreground 애플리케이션이 서비스를 갖는 애플리케이션에 바인딩하거나 콘텐츠 프로바이더를 사용하여 앱에 연결할 때.
    예를 들어 다른 앱이나 시스템이 아래의 내용에 바인딩하면 애플리케이션이 Foreground에 있는 것이다.
    • IME
    • Wallpaper Service
    • Notification listener
    • Voice 또는 text 서비스
    • 자동차에서 스트리밍을 듣는 음악 앱(Android Auto일 때)

위의 조건들을 만족하지 못한다면 애플리케이션이 Background에 있는 것으로 간주된다.

바인드된 서비스는 영향을 받지 않는다.

  • 이러한 규칙은 바인드된 서비스에는 어떠한 영향도 미치지 않는다. 앱이 바인드된 서비스를 정의하는 경우, 해당 앱이 Foreground에 있는 없든 간에 다른 구성요소들이 이 서비스에 바인드할 수 있다.

앱이 Foreground에 있는 동안에는 이 앱이 Foreground 및 Background 서비스를 자유롭게 생성하고 실행할 수 있다. 앱이 백그라운드로 이동하더라도 몇 분 동안은 앱이 서비스를 생성하고 사용하는 것이 여전히 허용된다. 이 기간이 끝나게 되면 앱이 유휴 상태로 간주된다. 이때 마치 앱이 서비스의 Service.stopSelf() 메소드를 호출한 것처럼 시스템이 앱의 백그라운드 서비스를 중지시킨다.

어떠한 상황에서는 백그라운드 앱이 몇 분 동안 임시 허용 목록이란 곳에 들어가기도 한다. 앱이 허용 목록에 있는 동안에는 제한 없이 서비스를 시작할 수 있으며 백그라운드 서비스도 실행이 허용된다. 사용자에게 보이는 다음과 같은 작업을 앱이 처리하는 경우에 앱이 허용 목록에 들어간다.

  • 우선순위가 높은 Firebase Cloud Messaging(FCM) 메시지 처리
  • SMS/MMS 메시지와 같은 브로드캐스트 수신
  • 알림에서 PendingIntent 실행

많은 경우 앱이 백그라운드 서비스를 JobScheduler 작업으로 대체할 수 있다. Android 8.0 이전에는 Foreground 서비스를 생성하는 일반적인 방법은 Background 서비스를 생성한 후 이 서비스를 Foreground 서비스로 승격시키는 것이었다.

Android 8.0에서는 좀 복잡하며 시스템은 백그라운드 앱이 Background 서비스를 생성하는 것을 허용하지 않는다. 이 때문에 Android 8.0에서는 새 서비스를 Foreground에서 시작하는 새로운 메소드 Context.startForegroundService()를 소개한다.

시스템이 서비스를 생성한 후, 앱은 5초 이내에 해당 서비스의 startForeground() 메소드를 호출하여 새 서비스의 알림을 사용자에게 표시해야 한다. 앱이 이 시간 내에 startForeground() 를 호출하지 않으면 시스템이 서비스를 중단하고 앱을 ANR로 선언하게 된다.

5. 사례와 해결 방법

백그라운드 실행 제한으로 인해 원하는 작업을 하는게 조금 어려워졌다. 개발자는 백그라운드 실행을 구현하기 위해 사용할 도구를 결정하려면 원하는 것을 명확하게 이해하고 어떤 제한 사항을 가지고 있어야 한다.

공식 문서에 나와있는 Guide to background Processing을 참고해서 알아보도록 하자.

개요
안드로이드 앱은 UI 처리, 사용자와 상호작용, LifeCycle 이벤트 수신 등을 담당하는 Main Thread가 존재한다. Main Thread에서 너무 많은 작업이 발생하면 앱이 끊기거나 느려져서 사용자 경험이 좋지 않다. 즉, 사용자가 불편함을 느낄 수 있다.

비트맵 디코딩, 디스크 접근 또는 네트워크 요청 수행과 같은 시간이 오래 걸리는 실행 및 작업을 별도의 백그라운드 스레드에서 수행해야 한다. 일반적으로 몇 밀리초 이상 걸리는 것은 모두 백그라운드 스레드에 위임해야 한다. 이러한 작업 중 일부는 사용자가 앱을 활발하게 사용하는 동안 수행되어야 한다. 백그라운드 스레드에서 작업을 실행하고 앱을 사용하는 동안 기본 UI 스레드에서 작업을 실행하는 방법 그리고 두 스레드 간 통신 방법에 대해서는 다른 글을 참고하면 좋다.

애플리케이션은 또한 사용자가 백엔드 서버와 정기적으로 동기화하거나 앱 내에서 새로운 컨텐츠를 가져오는 등 앱을 사용하지 않을 때에도 실행해야 하는 일부 작업이 필요할 수 있다. 또한 애플리케이션은 사용자가 앱과 상호작용을 완료한 후에도 서비스가 즉시 실행되도록 요구할 수 있다. 이와 같은 경우를 해결하기 위해서 백그라운드 처리에 대한 고민이 필요하다.

백그라운드 처리의 과제

백그라운드 작업은 RAM 및 배터리와 같은 기기의 제한된 리소스를 소비한다. 올바르게 사용하지 않을 경우 사용자에게 좋지 않은 경험을 제공할 수 있다.

안드로이드는 배터리를 극대화하고 좋은 앱 동작을 위해 앱 또는 Foreground Service Notification이 사용자에게 보이지 않을 때 백그라운드 작업을 제한한다.

다음은 위에서 언급한 버전별 백그라운드 정책의 변화를 보여준다.

  • Android 6.0 : Doze 모드와 App Standby(앱 대기) 기능
    • 도즈 모드는 화면이 꺼져 있고 기기가 정지해 있을 때 앱 동작을 제한한다.
    • 앱 대기에서는 사용하지 않는 애플리케이션을 네트워크 접근, 작업 및 동기화를 제한하는 특수 상태로 전환한다.
  • Android 7.0 : 암시적 브로드캐스트 리시버를 제한하고 개선된 도즈모드를 제공한다.
  • Android 8.0 : 백그라운드에서 위치를 파악하거나 캐시된 Wake lock을 해제하는 등 추가적인 백그라운도 동작 제한이 있다.
  • Android 9.0 : 앱 이용 패턴에 따라 자원에 대한 앱 요청이 동적으로 우선되는 App Standby Buckets를 소개했다.

결국 내가 해야 할 작업을 정확히 이해하고 백그라운드 작업을 버전별 정책에 맞도록 개발하는 것이 중요하다.

작업에 맞는 적합한 방법을 선택하자.

아래의 예시를 확인해보고 생각해보자.

  • 작업이 연기될 수 있거나 당장 일어날 필요가 있는가? 예를 들어, 사용자가 버튼을 클릭하는 것에 대응하여 네트워크에서 데이터를 가져와야 하는 경우 이 작업은 즉시 수행되어야 한다. 그러나 서버에 로그를 업로드하려면 앱의 성능이나 사용자 기대에 영향을 미치지 않고 작업을 연기할 수 있다.
  • 작업이 시스템에 상태에 따라 달라지는가? 장치에 전원 연결, 인터넷 연결 등과 같은 특정 조건을 충족할 때만 작업을 실행하기를 원할 수 있다. 예를 들어, 사용자의 앱은 주기적으로 저장된 데이터를 압축해야 할 수 있다. 사용자에게 영향을 주지 않도록 하려면 장치가 충전되고 유휴 상태일 때만 이 작업을 수행하기를 원할 것이다.
  • 작업이 정확한 시간에 실행되어야 하는가? 일정 관리 앱의 경우, 사용자가 특정 시간에 발생할 이벤트에 대해서 미리 알림을 설정하도록 할 수 있다. 사용자는 정확한 시간에 알림을 볼 수 있을 것으로 예상한다. 다른 경우, 앱은 작업이 실행될 때 정확하게 신경을 쓰지 않을 수 있다. 앱에는 "작업 A가 먼저 실행되야 하고, 그 다음에 작업 B가 실행되어야 하며 다음에는 작업 C가 실행되어야 한다."와 같은 일반적인 요구 사항이 있을 수 있다. 하지만, 특정 시간에 실행되기 위해서는 작업이 필요하지 않다.
  1. WorkManager

모든 OS 백그라운드 실행 제한을 고려하여 백그라운드 실행에 권장되는 솔루션이다. 장치 또는 애플리케이션이 재시작되더라도, 작업이 연기될 수 있거나 작업이 실행될 것으로 예상되는 경우 WorkManager를 사용할 수 있다.

작업 조건(네트워크 가용성, 배터 등)이 만족될 때, 지연 가능한 백그라운드 작업을 멋지게 실행할 수 있다.

장점으로는 작업(일회성 또는 반복성)을 예약하거나 작업을 결합(체이닝)할 수 있다. 또한 장치가 유휴상태이거나 충전 중일 때 특정 이벤트를 트리거하거나 콘텐츠 프로바이더가 변경될 때 실행하는 것과 같은 실행 제한 조건을 적용할 수 있다.

한 가지 예로 로그를 압축하여 서버에 업로드 해야 하는 경우라면 두 가지 작업 요청을 만들어 수행할 수 있다.

  1. 파일을 압축한다. -> 이 단계에서 장치가 충전 중이어야 한다는 제한 조건을 추가할 수 있다.
  2. 서버에 업로드 한다. -> 이 요청의 경우 네트워크가 사용 가능할 때만 작업이 실행되도록 네트워크 연결 제한 조건을 추가해야 한다.

두 작업을 모두 큐에 넣은 뒤 WorkManager와 함께 필요한 리소스가 충족할 때 작업을 수행하도록 할 수 있다. 또 다른 장점으로는 전원 관리 기능을 존중하는 것이다. WorkManager는 제약 조건이 충족되며, Doze가 해제된다면 주어진 작업을 실행할 것이다.

자세한 내용은 다음 포스팅에서 정리할 예정이다.

  1. Foreground Service

앱에서 음악, 비디오 재생 또는 탐색과 같이 앱을 종료하거나 화면을 꺼도 지연되지 않고 사용자가 시작한 작업을 완료해야 하는 경우 Foreground Service를 사용해야 한다. Foreground Service를 사용한다는 것은 중요한 일을 하고 있으므로 죽여서는 안된다는 것을 시스템에 알린다. 그리고 Notification Bar에 띄워서 Foreground Service가 수행 중임을 표시해야 한다.

  1. Alarm Manager

정확한 시간에 작업을 실행해야 하고 사용자와 상호 작용이 포함되며 지연될 수 없는 경우 사용하면 된다. Alarm Manager는 사용자가 지정한 시간에 필요한 경우 사용자의 앱을 실행한다.

그러나 작업이 정확한 시간에 실행될 필요가 없다면 WorkManager가 더 나은 방법이다. WorkManager는 시스템 자원의 균형을 더 잘 맞출 수 있다. 예를 들어, 매 시간마다 작업을 실행해야 하지만 특정 시간에 작업을 실행할 필요가 없는 경우, WorkManager를 사용하여 반복 작업을 설정하면 된다.

또한, 알람이 발생하면 작업을 짧은 시간내에 끝내야 한다. 네트워크에 접근하지 못할 수도 있다.(도즈모드나 앱 대기 버킷 때문에) 네트워크가 필요하거나 시간이 오래 걸리는 작업을 수행하려면 위에서 언급한 WorkManager를 사용해야 한다. 알람이 울릴 때마다 장치는 저전력 모드를 벗어나 부분적 wake-lock을 유지하므로 시간이 지남에 따라 배터리 수명에 상당한 영향을 줄 수 있다.

  1. Download Manager

사용자가 앱을 통해서 시간이 오래 걸리는 Http 다운로드를 수행하고 있다면 Download Manager를 사용하면 된다. 클라이언트는 URI를 앱 프로세스 외부에 있을 수 있는 특정 대상 파일로 다운로드하도록 요청할 수 있다. Download Manager는 백그라운드에서 다운로드를 수행하여 Http 상호 작용을 관리하고 실패 후 또는 연결 변경 및 시스템 재부팅 전반에 걸쳐 다운로드를 재시도 한다.

요약하면 아래와 같다.

파일을 다운로드 할 때 다운로드 매니저를 사용하면 될 것으로 예상이된다. 하지만, 다운로드 같은 경우 백그라운드에서 동작하고 시간이 오래 걸릴 수도 있기 때문에 백그라운드 정책에 위반될 상황이 생길 수도 있을 것 같다. 하지만, 다운로드 매니저가 내부적으로 어떻게 구현되어있는지 모르기 때문에 아직 확신할 수 없다. 관련 내용을 조금 더 찾아봐야 할 것 같다.

참고