본문 바로가기

안드로이드/Android Studio

[ Android ] Service

안드로이드 4대 컴포넌트(Activity, Service, Broadcast Reciever, Content Provider) 중 서비스에 대해 글을 쓰고자 합니다.

안드로이드 4대 컴포넌트

 

서비스란?

- 4대 컴포넌트 중 하나

- UI가 따로 없고 백그라운드에서 동작하는 컴포넌트

- 즉, 서비스를 실행한 앱에서다른 앱으로 전환하더라도 서비스에서 시작한 작업은 백그라운드에서 계속 실행된다.

- 만약 Service가 실행되고 있는 상태라면, 안드로이드 OS 에서는 해당 Process를 왠만하면 죽이지 않도록 방지하고 관리하게 된다.

- 그렇기에 메모리 부족 등 특별한 경우를 제외하고는 백그라운드 동작을 수행한다.

 

서비스가 필요한 이유

- Activity 화면에서의 동작 뿐 아니라, Activity가 종료되어 있는 상태에서도 동작하기 위해서 만들어진 컴포넌트이다.

- 예를 들어 음악 플레이어 같은 기능

- 화면이 종료된 상태에서도 음악은 계속 재생하죠?

- 물론 위와 같은 작업들은 백그라운드 스레드로 작업이 가능합니다.

- 하지만 백그라운드 스레드로 작업을 진행할 경우 유저가 앱의 프로세스를 강제 종료시키는 등의 상황에서

- 완전한 작업의 실행을 보장하지 못하기 때문에 서비스가 필요합니다.

 

서비스 라이프사이클

Application, Activity, Fragment에도 생명주기가 있듯이 서비스에도 생명주기가 있습니다.

 

              start타입                                     bind타입

 

 

 

서비스 타입

서비스는 크게 3가지 타입이 있습니다.

1. 포그라운드 서비스

- 현재 뭔가 하고 있다는 것을 사용자가 인지하고 있는 서비스

- 무조건 알림창으로 서비스가 실행중인 것을 표시해야함

- 시스템에 의해 강제 종료되지 않음.(메모리 부족 등으로 강제 종료 되지 않음)

- startForeground() 를 호출하여 서비스를 앞단에 둠.

 

2. 백그라운드 서비스

- 사용자에게 보이지 않는 백그라운드에서 작업을 수행

- 시스템에 의해 강제 종료될 수 있다.

- startService() 메서드 호출 or bindService()를 호출하여 실행

 

3. 바인드 서비스

- 서비스와 서비스를 호출하는 앱 구성 요소가 서버-클라이언트와 같은 형태로 상호작용

- 여러 프로세스에서 같은 서비스에 바인딩하여 작업 수행 가능

서비스 구현 방법

 

- startService

호출 과정

1) startService() 함수 호출로 서비스 시작

2) 한 번 시작되면, 백그라운드에서 무한정 실행되지만 보통 작업이 끝나면 서비스가 종료

3) 호출한 곳에 결과값을 반환하지 않고 계속해서 서비스 함

4) stopService() 함수 호출로 서비스 종료

 

위에서 startService()메서드로 서비스를 시작한다고 설명했지만 startService()를 호출하는 시점에 바로 시작되는 것은 아닙니다. 메인 Looper의 MessageQueue에 Message가 들어가서 메인 스레드를 쓸 수 있는 시점에 서비스가 시작됩니다.

startService()메서드는 곧바로 ComponentName을 리턴하고 다음 라인을 진행합니다.

startService()는 Intent Bundle에 파라미터를 전달하고 서비스에 작업하도록 요청하는 역할을 할 뿐입니다.

 

주요 함수

startService() : 서비스 실행

onCreate() : 서비스가 최초 생성될 때 한 번 호출. 이미 서비스가 실행중이라면 호출되지 않음.

즉, 서비스에 필요한 리소스 등을 준비하는 작업을 합니다.

onStartCommand() : 앱의 다른 컴포넌트에서 서비스를 실행할 때 호출. 이 메서드가 호출되면 서비스가 시작된 것이고 백그라운드에서 작업을 한다.

이름 그대로 명령을 매번 처리하는 역할을 한다.

onStartCommand() 메서드에서 백그라운드 스레드를 생성하고 작업을 진행하면 된다.

(백그라운드 스레드를 따로 만들지 않을 경우 UI 스레드 사용) => 밑에서 다시 다룹니다.

 

onStartCommand() 메서드는 3가지 리턴 타입을 갖는다.

 START_STICKY : Service가 강제 종료되었을 경우 시스템이 다시 Service를 재시작 시켜 주지만 intent 값을 null로 초기화 시켜서 재시작 합니다. ( = super.onStartCommand(intent, flags, startId) )

 Service 실행시 startService(Intent service) 메서드를 호출 하는데 onStartCommand(Intent intent, int flags, int startId) 메서드에 intent로 value를 넘겨 줄 수 있습니다. 기존에 intent에 value값이 설정이 되있다고 하더라도 Service 재시작시 intent 값이 null로 초기화 되서 재시작 됩니다.

 START_NOT_STICKY : 이 Flag를 리턴해 주시면, 강제로 종료 된 Service가 재시작 하지 않습니다. 시스템에 의해 강제 종료되어도 괸찮은 작업을 진행 할 때 사용해 주시면 됩니다.

 START_REDELIVER_INTENT : START_STICKY와 마찬가지로 Service가 종료 되었을 경우 시스템이 다시 Service를 재시작 시켜 주지만 intent 값을 그대로 유지 시켜 줍니다. startService() 메서드 호출시 Intent value값을 사용한 경우라면 해당 Flag를 사용해서 리턴값을 설정해 주면 됩니다.

 

onDestroy() : 서비스가 소멸될 때 호출.

stopService() : 다른 구성 요소가 서비스를 중단한다.

stopSelf() : 서비스가 스스로 중단

 

 

- boundService

 

호출 과정

1) bindService() 함수 호출로 서비스 시작.

2) 서버-클라이언트 형식의 구조로서 액티비티에서 서비스에게 요청하고 서비스는 그 요청을 처리한 후 결과값을 반환

3) 때문에 액티비티가 사라지면, 서비스도 자동적으로 destroy되면서 없어짐.

4) 또 하나의 서비스에 여러 개의 액티비티가 붙을 수 있다.

 

 

주요 함수

bindService() : 서비스에 바인딩할 때 사용

Context에 있는 bindService() 메서드 원형은 

public abstract bindService(Intent service, ServiceConnection conn, int flags) 이러합니다.

- service는 대상 서비스

- conn은 서비스와 연결되거나 연결이 끊길 때의 콜백

- flags는 0또는 Context의 BIND_~~등의 상수 혹은 || 연산자로 상수를 여러 개 넣어도됩니다.

이중 가장 많이 쓰이는 상수는 Context.BIND_AUTO_CREATE입니다.

 

BIND_AUTO_CREATE 

bindService()를 실행한다고 해서 서비스에 항상 바인딩 되는 것은 아닙니다.

서비스가 생성이 되어야만 바인딩이 가능한데, 이 때 서비스가 생성된 게 없다면 새로 생성하는 옵션이 BIND_AUTO_CREATE 상수 값을 bindService에 flag값으로 추가하는 것입니다.

 

또한, 서비스에 바인딩 된 클라이언트가 여러 개 남아있을 때 BIND_AUTO_CREATE 옵션이 있다면 stopService()를 실행해도 서비스가 종료되지 않습니다.

클라이언트마다 모두 unbindService()를 실행해야 비로소 Service의 onDestory()가 불립니다.

반면에 이 옵션이 없는 경우엔 stopService()를 실행하면 연결이 끊기고 바로 Service의 onDestroy()가 불립니다.

 

unbindService() : 서비스를 언바인딩할 때 사용

onBind() : 다른 컴포넌트가 서비스에 바인딩되면 호출

onUnbind() : unbindService() 호출 시 호출.

 

(서비스를 호출한 컴포넌트)

onServiceConnected() : 서비스에 바인드 됐을 때 호출.

onServiceDisconnected() : 서비스를 호스팅하는 프로세스가 중단되거나 종료되어 예기치 않게 서비스에 연결이 끊어졌을 때 호출된다. 클라이언트가 언바인딩 할 때는 호출되지 않는다

 

 

- intentService : 포그라운드 or 백그라운드 서비스

IntentService는 백그라운드 제한 정책이 적용되면서 deprecated 되었습니다. Android 8(API 26) 이상에서는 WorkManager나 JobIntentService를 이용할 수 있습니다.

IntentService가 Foreground에서만 실행된다면 문제 없지만, App이 Background로 전환되면 Service가 중단되거나 실행되지 않을 수 있습니다.

 

 

1) 포그라운드나 백그라운드 서비스에 상관없이 모든 서비스 클래스는 동시에 여러 개의 요청을 처리해야 하는 경우 

2) Service 클래스를 상속받고, Intent로 받은 여러 개의 요청을 작업 큐에 담아놓고 하나하나 처리하고 싶은 경우 Intetnt Service클래스를 상속받는다.

3) 일반 Service와 다르게 요청이 끝나면 자동으로 서비스가 종료.

4) 또 다른 서비스들과 다르게 onHandleIntent()함수 하나만을 통해 작업을 처리할 수 있음

주요 함수

onStartCommand() : 인텐트를 작업 큐로 보낸 후 onHandleIntent()를 호출.

onHandleIntent() : 워커 스레드에 의해 순차적으로 호출되어 필요한 작업을 수행한다.

 

서비스 수행 처리 Example

 

1) 액티비티 1에서 startService() 호출

2) 액티비티 2에서 bindService() 호출

3) 액티비티 2에서는 서비스바운딩을 통해 현재 실행 중인 서비스 객체에 접근 가능

4) 이때, 액티비티 1에서는 startService()로 서비스를 실행했기에 서비스 객체에 접근이 불가

5) 하지만 startService()호출 후 bindService()를 호출함으로써 서비스 객체에 접근 가능하게 됨

6) 이렇게 하나의 서비스에 대해 여러 서비스바운딩이 이뤄진 경우 서비스가 종료되는 시점에 대해 어려울 수 있음

7) startService()만 호출했을 경우 stopService()만으로 서비스를 종료할 수 있지만

8) 위 그림 처럼 어딘가에서 서비스바운딩이 일어났을 때는 바운딩이 끊어져야 서비스가 종료됨.

9) 즉, 서비스바운딩 중에 stopService()를 호출해도 바운딩이 끊어질 때까지 기다리며 서비스바운딩 중인 액티비티에서 바운딩이 끊어져야 onDestroy()가 호출되며 서비스가 종료됨.

10) 또한 startService()호출 없이 bindService()만 호출해서 서비스를 시작할 수 있으며 unbindService()를 호출하면 onDestroy()가 호출됨과 동시에 서비스 수행이 종료됨.

 

참고

'onServiceDisconnected()' 메서드가 호출되는 경우는 서비스로의 연결이 예기치 못하게 끊어졌을 때,

즉 서비스가 출돌했거나 중단되었을때 등 입니다.

클라이언트가 바인딩을 해제한다고 이것이 호출 되지 않습니다.

 

 

 

사용시 주의점

안드로이드는 리눅스 기반이기 때문에 프레임워크단에는 리눅스로 구현되어있다.

메모리관리 또한 리눅스 정책을 따른다. 즉 리눅스 커널에 의해서 관리된다.

결국 여러 프로세스들을 커널에서 관리한다고 짐작할 수 있다. 또 하나의 프로세스안에는 어플리케이션, 4대 컴포넌트, 스레드등을 구성하고있다. 이 말은 4대컴포넌트의 운명 또한 리눅스 커널에게 달려있다는 소리다.

 

만약 메모리부족이나, 과부하 등과 같은 현상이 발생했을 때 리눅스 커널이 프로세스를 강제로 종료시킬 수 있다.

안드로이드 어플리케이션적으로 설명하자면, 안드로이드 ui관련 처리는 메인스레드(UI스레드)라는 녀석이 처리하는데 

서비스를 그냥 생성하면, 기본적으로 이 메인스레드에게 붙는다. 만약 서비스에게 많은 일을 할당하면, 메인스레드에게 부담이 된다. 

 

따라서 서비스는 UI 스레드에서 실행한다는 것을 알아둬야됩니다.

서비스에게 많은 일을 해야하는 경우가 발생하면, 서비스의 일은 곧 메인스레드이고, 메인스레드는 그일을 처리하느냐 UI업데이트를 신경쓰지 못하고 사용자는 멈춰있는 어플리케이션을 마주하게된다. 구글은 사용자가 어플리케이션 UI업데이트를 오랫동안 기다려야하고 늦은 응답에 대해 무한정 기다려주는 정책을 펼치지 않는다. 이 때 ANR이라는 것을 발동시켜 강제로 프로세스를 죽여버린다. 

 

그렇다면, ANR을 방지하기 위해... 만약 많은 일을 서비스가 처리해야한다면, 별도의 WorksThread를(백그라운드 스레드) 만들어 처리해야한다.

여기서 주의 해야 할 점은, 모든 컴포넌트들이 Main Thread 안에 실행된다는 점 입니다. Main Thread는 앞서 Thread의 예제에서 살펴봤듯이, UI 작업을 처리해주는 Thread 입니다. Service 역시 Main Thread에서 관리하는 녀석이므로, Thread 작업이 필요한 경우, 작업Thread를 생성해서 관리해줘야 한다는 점 입니다.

아니면 ANR이 발생하여, 종료시켜 버리게 됩니다. 그렇기 때문에 Service사용시에도 Thread작업이 필요할 경우에는, 별도의 작업Thread를 만들어서 사용해야 합니다. 이점 유의해 주시기 바랍니다.

 

 

 

반응형