Service의 종류
Android의 서비스는 간단히 구분하면, 내부/외부 서비스로 나눠진다.
내부 서비스는 말그대로 앱 안에서 서비스를 호출하는걸 말하고, 외부 서비스는 앱간 호출이 가능한 서비스라고 할 수 있다.
내부 서비스 역시 startService()로 서비스를 띄우는 로컬서비스와 bindService()를 통하여 서비스를 띄우는 bind 서비스로 나눠볼 수 있다.
이 글에서는 내부 bind service에 대해서 다루고자 한다.
bind Service를 이용한 통신
srartService()로 서비스를 만드는경우 intent에 번들로 어떤 정보를 전달할 수 는 있지만, 일단 서비스가 시작되면 초기 전달 값 이외에는 호출부분과 통신할 수 없다.
예를 들면 아래와 같은 작업은 서비스를 startService()를 사용하고 서비스를 이용할수 있다.
ex1) activity에서 버튼을 누르면 sync를 시작하는 서비스가 뜬다.
ex2) activity에서 버튼을 누르면 sync를 시작하는 서비스가 뜨고, 서비스는 sync 동작 결과를 notification에 표시한다.
하지만 아래와 같은 경우는 어떻까?
ex3) activity에서 버튼을 누르면 sync를 시작하는 서비스가 뜨고, sync의 진행 상태를 activity에 주기적으로 전달한다.
ex4) activity에서 버튼을 누르면 sync를 시작하는 서비스가 뜨고, sync의 진행 상태를 activity에 주기적으로 전달한다. 또한 activity는 service에 sync 정지 명령을 전달할 수 있다.
정리하면, ex1),ex2)는 서비스와 호출부(activity간) 통신을 하지 않는다. service가 일단 시작되면, 알아서 작업을 처리한다.
ex3), ex4)의 경우 서비스와 호출부간 단방향, 또는 양방항 통신이 이루어져야 한다.
그럼 AIDL을 이용하여 bind Service를 띄우고, RPC를 이용하면 되지 않는가?
물론 이 방법이 범용적으로는 맞을수 있으나, 내부 component간 통신은 handler를 통해서도 가능하다. Thread간 통신에만 handler를 쓰는것이 아니라 서비스와 호출 component간에도 handler를 이용하여 서로 메시지를 주고 받을 수 있다.
AIDL을 이용한 방법과 handler를 이용한 방법에 장 단점에 대해서는 마지막에 설명한다.
여담이지만, 예제로 들었던 sync는 service에서 단순히 처리하면 안된다. service역시 main thread에서 동작하므로 network을 이용한 sync라면 아래와 같은 방법으로 처리해야 한다
1. IntentService를 사용하여 sub thread를 이용
2. Service 내부에서 thread를 띄워서 이용
3. SyncManager를 이용하여, framework에서 sync를 진행하도록 구현.
서비스에서 사용하는 FLAG
내부 양방향 통신을 설명하기 전에 서비스에서 많이 사용하는 flag에 대한 정리를 먼저 진행한다.
1. startService
- onCreate() -> onStartCommand()가 호출되면서 서비스가 시작된다.
이때 onStartCommnad()의 반환값으로 아래와 같은 flag를 사용한다.
-> START_STICKY, START_STICKY_COMPATIBILITY, START_REDELIVER_INTENT
: 서비스가 죽으면, 시스템이 지속적으로 서비스를 실행 시킨다.
안드로이드 우선순위에 의해 메모리 부족시 service는 LMK에 의해 죽을 확률이 크다.
이때, service가 죽으면 시스템이 다시 살려준다.
재시작시 onStartCommand에 기존에 넘겨받은 intent를 그대로 넘겨줄지 null인채로 넘겨줄지는 다르다.
-> START_NOT_STICKY
: 서비스가 죽거나, 문제가 있어 시작되지 못할경우, 그냥 시작되지 않는 채로 남는다.
사용자가 다시 요청을 통하여 service를 실행해야 한다.
2.bindService
- onCreate() -> onBind()가 호출된다. 이때 bindService의 parameter로 아래와 같은 flag를 사용한다.-> BIND_AUTO_CREATE
: Component와 연결되있는 동안 비정상적으로 종료시 자동으로 다시 시작된다.
-> BIND_DEBUG_UNBIND
: 비정상적으로 연결이 끊어지면 로그를 남긴다 (디버깅용)
-> BIND_NOT_FOREGROUND
: 백그라운드로만 동작한다. 만약 Activity에서 생성한 경우 Activity와 생성주기를 같이 한다.
Internal bindService #1
아래에서 예제로 제시할 샘플은 Activity에서 bindService를 이용하여 service를 띄우는 예제이다.
이때, Activity에서 service의 어떤 함수를 실행시키고 싶다면 binder를 이용하여 service 객체 자체를 activity에 전달해 줄 수 있다.
즉 activity -> service로의 단방향 통신이 가능하다.
service 부분
호출부분(activity)
단!, 이때의 Service는 Activity의 생명주기를 따른다. 즉 Activity가 종료되면, service도 onDetroy()가 불린뒤 종료된다.
Internal bindService #2
여기서는 Activity와 service가 양방향 통신을 하기 위해 handler를 이용하는 방법을 사용한다.
단 handler는 binder를 통하여 서로 넘겨줄수 없으니, handler를 wrapping할 수 있는 Messenger 객체를 이용한다.
Service의 구현
Activity 부분
※ Service에서는 Messenger.getBinder()를 이용하고, Activity에서는 Message.replyTo = Messenger를 이용하여 서로 handler를 주고 받는다.
만약 서비스를 아래와 같이 다른 process로 띄운다 해도 문제없이 동작한다.
단!. Activity의 회전, Back key로 종료(onDestroy), Android 설정 변경시 Service역시 onDestroy()가 불리면서 종료된다.
<service android:name=".xxxService" android:process=":remote"/>
AIDL을 이용한 외부 bind service의 경우도 마찮가지로 onDestroy()가 불린다. 단! 서비스의 process는 종료되지 않는다.
다만 아래와같이 AndroidManifest.xml을 이용하여, activity에 configChanges 속성을 임의로 설정하면, 서비스의 종료를 막을 수 있다.
<activity android:name="xxxActivity"
...
android:configChanges="locale|navigation|orientation" />