안드로이드에는 필수적인 앱 기본 구성 요소로 activity, service, broadcast receiver, content provider 4가지를 가집니다. 이전 글에 이어 service의 bound service에 대해 설명하겠습니다. 이 포스트로 service에 대한 설명을 마칩니다
Bound service란 클라이언트-서버 인터페이스 안의 서버를 말하며 일종의 Service 클래스 구현으로, 이를 통해 다른 애플리케이션이 service에 바인딩하여 상호작용할 수 있도록 합니다. 이를 사용하면 component를 service에 바인딩하고, 요청을 보내고, 응답을 수신하며, 프로세스 간 통신(IPC)을 실행할 수 있습니다.
연결을 오래 유지하기위해 bindService()를 호출하여 애플리케이션 component를 service에 바인딩하는 겁니다. 일반적으로는 startService()를 호출하더라도 component가 service를 시작하도록 허용하지 않습니다. activity나 다른 component에서 service와 상호작용하기를 원하는 경우 bound service를 생성해야 합니다. 아니면 애플리케이션의 기능 몇 가지를 프로세스 간 통신(IPC)을 통해 다른 애플리케이션에 노출하고자 하는 경우에도 사용됩니다.
bound service를 생성하려면 클라이언트가 service와 통신할 수 있는 인터페이스를 정의해야 합니다. 바인딩을 제공하는 onBind() 콜백 메서드를 구현하여 인터페이스를 정의하는 IBinder를 반환하도록 해야 합니다. 그럼 클라이언트는 이 IBinder를 받아 해당 인터페이스를 통해 service와 상호작용 할 수 있습니다. 즉 다른 component가 bindService()를 호출하여 해당 인터페이스를 탐색하고, service에 있는 메서드를 호출할 수 있습니다. 일반적으로 bound service는 다른 애플리케이션 component에 사용될 때까지만 유지되고 백그라운드에서 무한히 실행되지는 않습니다. 그렇기 때문에 service에 바인딩된 component가 없으면 시스템이 이를 소멸시킵니다. 따로 중단하지 않아도 됩니다.
여러 클라이언트가 service에 한꺼번에 바인딩될 수 있습니다. 클라이언트가 service와의 상호작용이 끝나면 unbindService()를 호출하여 바인딩을 해제합니다. service에 바인딩된 클라이언트가 하나도 없으면 시스템이 해당 servcie를 소멸시킵니다.
Android 시스템이 service를 강제 중단하는 것은 메모리가 부족하여 사용자 포커스를 가진 activity를 위해 시스템 리소스를 회복해야만 하는 경우로만 국한됩니다. service가 사용자 포커스를 가진 activity에 바인딩 되었으면 종료될 가능성이 적고 service가 포그라운드에서 실행되도록 선언된 경우에는 종료될 가능성이 희박합니다.
bound service를 구현하는 데에는 여러 가지 방법이 있지만 started service보다 훨씬 복잡합니다. 다음 내용에 이를 확인 할 수 있습니다.
바인딩을 제공하는 service를 생성하기 위해, 클라이언트가 service와 상호작용하는데 사용할 수 있는 프로그래밍 인터페이스를 제공하는 IBinder를 제공해야 합니다. 인터페이스를 정의하는 방법은 세 가지가 있습니다.
바인더 클래스 확장
메신저 사용
AIDL 사용
service가 로컬 애플리케이션에서만 사용되고 여러 프로세스에서 작동할 필요가 없을 때 사용하며 가장 보편적인 경우입니다. 예를 들어 음악 앱의 경우 백그라운드에서 음악을 재생하는 자체 service에 activity를 바인딩합니다. 자체적인 Binder 클래스를 구현하여 클라이언트가 service 내의 public 메서드에 직접 액세스하도록 할 수도 있습니다. 설정하는 방법은 다음과 같습니다.
service에서 다음 중 한 가지 기능을 하는 Binder의 인스턴스를 생성합니다.
Binder의 인스턴스를 onBind() 콜백 메서드에서 반환합니다.
클라이언트에서 Binder를 onServiceConnected() 콜백 메서드에서 받아서 제공된 메서드로 bound service를 호출합니다.
service와 클라이언트가 같은 애플리케이션에 있어야 클라이언트가 반환된 객체를 전송하여 그 API를 적절하게 호출할 수 있습니다. 또한 service와 클라이언트는 같은 프로세스에 있어야 하기도 합니다. 이 방법에서는 여러 프로세스에서의 마샬링을 전혀 실행하지 않기 때문입니다.
다음 예시는 service가 Binder 구현을 통해 service 내의 메서드에 액세스할 수 있는 권한을 클라이언트에 제공하는 것을 보여줍니다.
public class LocalService extends Service {
// 클라이언트에 제공된 바인더
private final IBinder binder = new LocalBinder();
// 랜덤 숫자 생성기
private final Random mGenerator = new Random();
/**
*클라이언트 바인더에 사용되는 클래스입니다. 이 service는 항상 클라이언트와
* 동일한 프로세스에서 실행되므로 IPC를 처리할 필요가 없습니다.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// LocalService의 this 인스턴스를 리턴하기에 클라이언트는 public 메소드들을 호출할 수 있습니다.
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
/** 클라이언트를 위한 메소드 */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
LocalBinder는 LocalService의 현재 인스턴스를 검색하기 위한 getService() 메서드를 클라이언트에 제공합니다. 이렇게 하면 클라이언트가 service 내의 public 메서드를 호출할 수 있습니다. 예를 들어 클라이언트는 service에서 getRandomNumber()를 호출할 수 있습니다.
다음은 버튼을 클릭했을 때 LocalService에 바인딩되어 getRandomNumber()를 호출하는 활동을 나타냅니다.
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// LocalService로 바인드
Intent intent = new Intent(this, LocalService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(connection);
mBound = false;
}
/** 버튼이 클릭되었을 때 호출(레이아웃 파일의 버튼에서 android:onClick
속성으로 이 메소드를 결합 **/
public void onButtonClick(View v) {
if (mBound) {
// LocalService에서의 메소드를 호출. 그러나 이 호출이 중단될 경우,
// 활동 성능 저하를 방지하기 위해 별도의 스레드에서 이 요청을
// 발생해야 합니다.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** bindService()에 전달된 service 바인딩에 대한 콜백을 정의합니다. */
private ServiceConnection connection = new ServiceConnection() {
//onBind()가 반환한 IBinder를 전달
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// LocalService로 바인드하여 IBinder를 캐스트하고
//LocalService 인스턴스를 가져옵니다.
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
위 예시는 클라이언트가 ServiceConnection 구현과 onServiceConnected() 콜백을 사용하여 service에 바인딩되는 방법을 보여줍니다. onStop() 메서드는 service에서 클라이언트의 바인딩을 해제합니다. 적절한 시기에 service에서 클라이언트의 바인딩을 해제해야 합니다
service가 원격 프로세스와 통신해야 한다면 Messenger를 사용하여 service에 인터페이스를 제공할 수 있습니다. 이 방법을 사용하면 AIDL을 쓰지 않고도 프로세스 간 통신(IPC)을 실행할 수 있습니다.
인터페이스에 Messenger를 사용하는 것은 AIDL을 사용하는 것보다 더욱 간단합니다. Messenger는 모든 service 호출을 큐에 올리기 때문입니다. 순수한 AIDL 인터페이스는 service에 동시 요청을 보내고, 이를 받은 service는 다중 스레딩을 처리해야 합니다. 대부분의 애플리케이션에서는 service가 다중 스레딩을 처리할 필요가 없으므로 Messenger를 사용하여 한 번에 하나씩 호출을 처리할 수 있습니다. service의 다중 스레딩 처리가 중요한 경우, AIDL을 사용하여 인터페이스를 정의해야 합니다.
사용 방법은 다음과 같습니다.
handleMessage() 메서드를 사용합니다.이렇게 하면 클라이언트가 service에서 호출할 메서드가 없습니다. 대신 클라이언트는 메시지(Message 객체)를 전달하여 service가 Handler로 받을 수 있도록 합니다.
다음은 Messenger 인터페이스를 사용하는 service의 간단한 예시입니다.
public class MessengerService extends Service {
// service에 메시지를 표시하는 명령
static final int MSG_SAY_HELLO = 1;
//클라이언트에서 오는 메시지 Handler
static class IncomingHandler extends Handler {
private Context applicationContext;
IncomingHandler(Context context) {
applicationContext = context.getApplicationContext();
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
// 클라이언트가 IncomingHandler에 메시지를 보낼 수 있게 하는 대상
Messenger mMessenger;
/**
* service에 바인딩할 때 service에 메시지를 보내기 위해
* 메신저에 인터페이스를 반환
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
mMessenger = new Messenger(new IncomingHandler(this));
return mMessenger.getBinder();
}
}
참고로 Handler의 handleMessage() 메서드에서 service가 수신되는 Message를 받고 what 멤버에 기초하여 무엇을 할지 결정합니다.
클라이언트는 service가 반환한 IBinder에 기초하여 Messenger를 생성하고 send()로 메시지를 전송하기만 하면 됩니다. 예를 들어, 다음은 service에 바인딩되어 MSG_SAY_HELLO 메시지를 service에 전달하는 간단한 활동입니다.
public class ActivityMessenger extends Activity {
/**service와 통신하기 위한 메신저 */
Messenger mService = null;
/** service에서 바인드를 호출했는지 여부를 나타내는 flag */
boolean bound;
// service의 주요 인터페이스와 상호작용을 위한 클래스
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// service와의 연결이 설정된 경우 이를 호출하여 service와
// 상호 작용하는데 사용할 수 있는 개체를 제공.
// 메신저를 사용하여 service와 통신하기 때문에
// 여기서는 원시 IBinder 객체에서 클라이언트 측 표현을 얻음
mService = new Messenger(service);
bound = true;
}
public void onServiceDisconnected(ComponentName className) {
// 서비스와의 연결이 예기치 않게 끊어졌을 때,
// 즉 프로세스가 중단되었을 때 이를 호출
mService = null;
bound = false;
}
};
public void sayHello(View v) {
if (!bound) return;
// 기본적으로 지원되는 'what' 값을 사용하여 service에 메시지 생성 및 전송
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
//service에 바인드
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// service 해제
if (bound) {
unbindService(mConnection);
bound = false;
}
}
}
이 예시는 service가 클라이언트에 응답하는 방식을 보여주지는 않습니다. service가 응답하게 하려면 클라이언트에서 Messenger도 생성해야 합니다. 클라이언트가 onServiceConnected() 콜백을 받으면, send() 메서드의 replyTo 매개변수에 클라이언트의 Messenger를 포함하는 service에 Message를 전송합니다.
AIDL는 자주 사용되는 내용도 아니고 복잡하기 때문에 자세한 내용은 생략합니다. 관심 있으신 분은 공식 문서를 확인해주시길 바랍니다.
service을 바인딩하려면 bindService()를 호출하면 됩니다. 그러면 Android 시스템이 service의 onBind() 메서드를 호출하고, 이 메서드가 service와의 상호작용을 위한 IBinder를 반환합니다.
바인딩은 비동기식으로 처리되고, IBinder를 클라이언트에 반환하지 않은 상태에서 bindService()가 즉시 반환됩니다. bindService()의 반환 값은 요청된 service가 존재하는지, 클라이언트에 service 액세스 권한이 있는지 나타냅니다.
IBinder를 수신하기 위해 클라이언트는 serivce와의 연결을 모니터링하는 ServiceConnection를 구현하여 인스턴스를 생성하고 이를 bindService()에 전달해야 합니다.
Android 시스템이 클라이언트와 service 사이에 연결을 생성하면 ServiceConnection에서 onServiceConnected()를 호출합니다. onServiceConnected() 메서드에는 IBinder 인수가 포함되고 클라이언트는 사용하여 bound service와 통신합니다.
Activity, service, content provider만 service에 바인딩할 수 있으며, broadcast receiver에서는 service에 바인딩할 수 없습니다.
클라이언트에서 service에 바인딩하는 방법은 다음과 같습니다.
ServiceConnection을 구현합니다.이 구현으로 두 가지 콜백 메서드를 재정의해야 합니다.
onServiceConnected()onBind() 메서드가 반환한 IBinder를 전달합니다.onServiceDisconnected()bindService()를 호출하여 ServiceConnection 구현을 전달합니다. 메서드가 false를 반환하면 클라이언트가 service에 제대로 연결되지 않았다는 것을 의미합니다. false이라도 클라이언트는 unbindService()를 호출해야 합니다. 그렇지 않으면 클라이언트가 유휴 상태인 service를 종료하지 못하게 합니다.
시스템이 onServiceConnected() 콜백 메서드를 호출하면, 인터페이스가 정의한 메서드를 사용하여 service에 호출을 시작할 수 있습니다.
service로부터 연결을 해제하려면 unbindService()를 호출합니다. 클라이언트가 service에 여전히 바인딩 되었지만 앱이 클라이언트를 소멸시킬 때 이 소멸로 인해 클라이언트가 바인딩 해제됩니다. 클라이언트가 service와의 상호작용을 완료하는 즉시 바인딩을 해제하는 편이 좋습니다. 이렇게 하면 유휴 상태인 service를 종료할 수 있습니다.
다음 예시에서는 바인더 클래스를 확장해서 생성한 service에 클라이언트를 연결합니다. 여기에서는 반환된 IBinder를 LocalBinder 클래스로 전송하고 LocalService 인스턴스를 요청하기만 하면 됩니다.
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// service와의 연결이 설정되면 호출
public void onServiceConnected(ComponentName className, IBinder service) {
//자체 프로세스에서 실행되는 명시적 service에 바인딩 되기 때문에
// IIBinder를 특정 클래스로 캐스팅하여 직접 액세스할 수 있음
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
//service와의 연결이 예기치 않게 끊어지면 호출
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
다음 예시에서와 같이, ServiceConnection이 있으면 클라이언트는 이를 bindService()에 전달하여 service에 바인딩할 수 있습니다.
Intent intent = new Intent(this, LocalService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
bindService()의 첫 번째 매개변수는 바인딩할 service의 이름을 명시적으로 지정하는 Intent입니다. 인텐트를 사용하여 Service에 바인딩하는 경우 명시적 인텐트를 사용하여 앱의 보안을 유지시켜야 합니다. 암시적 인텐트를 사용하여 service를 시작하면 어떤 service가 인텐트에 응답할지 확신할 수 없고 어떤 service가 시작하는지 사용자가 알 수 없으므로 보안 위험이 있습니다. Android 5.0(API 수준 21)부터는 암시적 인텐트로 bindService()를 호출하면 시스템에서 예외가 발생합니다.
두 번째 매개변수는 ServiceConnection 객체입니다.
세 번째 매개변수는 바인딩 옵션을 나타내는 플래그입니다. 일반적으로는 BIND_AUTO_CREATE가 되는데, 이는 service가 아직 활성화되지 않았을 경우 service를 생성하기 위함입니다. 그 외에는 BIND_DEBUG_UNBIND와 BIND_NOT_FOREGROUND를 사용할 수 있고 값이 없으면 0으로 설정합니다.
다음은 service로의 바인딩에 관한 몇 가지 중요한 참고사항입니다.
onStart()(바인딩)-> onStop()(바인딩 해제) onCreate()(바인딩)-> onDestroy()(바인딩 해제)onResume()과 onPause() 중에는 바인딩하거나 바인딩을 해제하지 말아야 함. 이러한 콜백은 모든 수명 주기 전환에서 발생하고 이런 전환에서 발생하는 처리는 최소한으로 유지해야 하기 때문. 위에 언급되었듯이 시작되었으면서도 바인드된 service를 만들 수 있습니다. 다시 말해, startService()를 호출하여 service를 시작하고 이를 통해 service가 무한히 실행되면서 bindService()를 호출하여 클라이언트가 service에 바인딩되도록 할 수 있다는 의미입니다. service가 시작되고 바인드되도록 허용한다면, service가 실제로 시작되었을 때 시스템은 클라이언트가 모두 바인딩을 해제해도 서비스를 소멸시키지 않습니다. 그 대신 service를 직접 확실히 중단해야 합니다. 그러려면 stopSelf() 또는 stopService()를 호출하면 됩니다.
보통은 onBind() 또는 onStartCommand()를 구현하지만, 둘 다 구현해야 할 때도 있습니다. 예를 들어 음악 플레이어의 경우 service를 무한히 실행하면서 바인딩도 제공하는 것이 유용할 수 있습니다. 이 경우 어떤 activity에서 service를 시작해 음악을 재생하면, 사용자가 애플리케이션을 닫아도 음악이 계속 재생됩니다. 그런 다음, 사용자가 애플리케이션으로 다시 돌아오면 이 activity를 service에 바인딩하여 재생 제어권을 다시 획득할 수 있습니다.
여러 클라이언트를 하나의 service와 동시에 연결할 수 있습니다. 그러나 시스템이 IBinder service 통신 채널을 캐시합니다. 다시 말해, 첫 번째 클라이언트가 바인딩될 때만 시스템이 service의 onBind() 메서드를 호출해 IBinder를 생성합니다. 그러면 시스템이 동일한 service에 바인딩되는 모든 추가 클라이언트에 동일한 IBinder를 전달합니다. onBind()는 다시 호출하지 않습니다. 마지막 클라이언트가 service에서 바인딩을 해제하면 시스템은 service를 소멸시킵니다. 단, service가 startService()로 시작되었을 경우는 예외입니다. bound service 를 구현할 때 가장 중요한 부분은 onBind() 콜백 메서드가 반환하는 인터페이스를 정의하는 것입니다.

모든 클라이언트에서 service가 바인딩 해제되면 Android 시스템이 이를 소멸시킵니다. 물론 startService() 호출과 함께 시작된 경우는 예외입니다. 따라서 service가 순수하게 bound service일 경우에는 service의 수명 주기를 관리하지 않아도 됩니다. 클라이언트에 바인딩되었는지 여부에 따라 Android 시스템이 대신 관리해주기 때문입니다. 그러나 onStartCommand() 콜백 메서드 구현을 선택하는 경우라면 service를 확실히 중지해야 합니다. service가 지금 시작된 것으로 간주되기 때문입니다. 이 경우 service가 클라이언트에 바인딩되어 있는지 여부와 관계없이, stopSelf()를 통해 service가 스스로 중지되거나 다른 component가 stopService()를 호출할 때까지 service가 실행됩니다.
또한 service가 시작되고 바인딩을 허용하는 경우 시스템에서 onUnbind() 메서드를 호출한 다음 클라이언트가 service에 바인딩 될 때, onRebind() 호출을 수신하고 싶다면 true를 선택적으로 반환할 수 있습니다. onRebind()는 void를 반환하지만 클라이언트는 여전히 onServiceConnected() 콜백에서 IBinder를 수신합니다. 위의 그림은 이런 종류의 수명 주기에 관한 로직을 보여줍니다.
이전 SERVICE 포스트
<service_1>
<service_2>
다음 SERVICE 포스트
<service 예제>