HOME > android > basic

안드로이드 - Messenger를 이용하여 Remote Service 구현하기

JSFollow05 Mar 2018

Remote Service는 App의 Main process와 다른 Process에서 실행되는 Service를 말합니다. MainActivity의 Process가 A process에서 실행이된다면, Service는 B process에서 실행됩니다. A와 B 프로세스는 서로 통신을 하여 동작을 해야 하기 때문에, 이 부분에 대해서 구현이 따로 필요합니다.

안드로이드에서는 RPC(Remote Procedure Call)를 Binder를 통해서 지원을 하는데, Binder로 매번 RPC를 구현하기는 귀찮고 번거롭기 때문에 좀 더 간단한 두가지 방법을 제공해주고 있습니다.

그 방법은 아래와 같습니다. 여기서는 Messenger class를 이용하여 RPC를 구현해보겠습니다.

  1. AIDL(Android Interface Definition Language)
  2. Messenger Class

Remote Service 설정

Service 객체를 생성하였다면, AndroidManifest.xml에서 Service를 정의하고 Remote Service로 설정하여야 합니다.

Service TAG에서 android:process=":remote" 로 선언해야 Main process와는 다른 Process에서 Service가 실행됩니다.

<service
    android:name=".services.MsgService"
    android:process=":remote"/>

RPC 구현

Activity객체를 생성하고, Service와 서로 통신할 수 있는 RCP 코드를 작성해야합니다. 이 둘은 다른 프로세스에 있기 때문에, 동일 프로세스가 아니기 때문에 Class를 참조하여 Function Call 하는 방법으로 서로 통신할 수 없습니다. 프로세스가 달라 자원이 서로 다르기 때문에, 통신하려면 Binder로 통신을 해야 하는데 이 까다로운 작업을 Messenger라는 객체가 쉽게 할 수 있도록 도와줍니다.

Messenger를 생성하는데 필요한 것은 Binder와 Handler입니다. Binder를 이용하여 다른 Process와 binding하며 다른 Process에서 보낸 event는 Handler를 통해 받을 수 있습니다.

Activity에서 Service에 bind

아래처럼 ServiceConnection을 정의하고 bindService를 통해 Service에 bind합니다.

Log.d(TAG, "Trying to connect to service");
Intent i = new Intent(getApplicationContext(), MsgService.class);
bindService( i, mConnection, Context.BIND_AUTO_CREATE);


private ServiceConnection mConnection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG, "onServiceConnected");
        mServiceCallback = new Messenger(service);

        // connect to service
        Message connect_msg = Message.obtain( null, MsgService.MSG_CLIENT_CONNECT);
        connect_msg.replyTo = mClientCallback;
        try {
            mServiceCallback.send(connect_msg);
            Log.d(TAG, "Send MSG_CLIENT_CONNECT message to Service");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
}

@Override
public void onServiceDisconnected(ComponentName name) {
    Log.d(TAG, "onServiceDisconnected");
    mServiceCallback = null;
}
};

Serivce는 onBind를 통해서 Activity에 IBind 객체를 return해줍니다. 여기서 Service는 Messenger 객체를 생성하였고 Messenger객체가 IBinder객체를 넘겨줍니다. 위에서 말했듯이 Messenger객체가 까다로운 RPC의 binder 처리를 하기 때문입니다.

또한 Messenger객체를 생성할 때 Handler를 생성하여 Parameter로 넘겨주는데, 이것은 Actvity에서 Service로 이벤트를 보낼 때 Handler를 통해 이벤트를 받을 수 있습니다. 따라서, 개발자는 Handler만 신경쓰면 되기 때문에 복잡한 Binder에 대한 것은 신경쓰지 않아도 됩니다.

public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
}



final Messenger mMessenger = new Messenger( new CallbackHandler());

private class CallbackHandler  extends Handler {
    @Override
    public void handleMessage( Message msg ){
        switch( msg.what ){
            case MSG_CLIENT_CONNECT:
                Log.d(TAG, "Received MSG_CLIENT_CONNECT message from client");
                mClientCallbacks.add(msg.replyTo);
                break;
            case MSG_CLIENT_DISCONNECT:
                Log.d(TAG, "Received MSG_CLIENT_DISCONNECT message from client");
                mClientCallbacks.remove(msg.replyTo);
                break;
            case MSG_ADD_VALUE:
                Log.d(TAG, "Received message from client: MSG_ADD_VALUE");
                mValue += msg.arg1;
                for (int i = mClientCallbacks.size() - 1; i >= 0; i--) {
                    try{
                        Log.d(TAG, "Send MSG_ADDED_VALUE message to client");
                        Message added_msg = Message.obtain(null, MsgService.MSG_ADDED_VALUE);
                        added_msg.arg1 = mValue;
                        mClientCallbacks.get(i).send(added_msg);
                    }
                    catch( RemoteException e){
                        mClientCallbacks.remove( i );
                    }
                }
                break;
        }
    }
}

Service가 Activity로 IBinder를 넘겨주면 Activity의 onServiceConnected가 Callback되면서 parameter로 IBinder를 받을 수 있습니다.

Activity에는 2개의 Messenger를 생성해야하는데, 하나는 1)Activity=>Service로 이벤트를 보낼 수 있는 통로 역할을 하는 Messenger이고 다른 하나는 2)Service=>Activity로 이벤트를 보낼 수 있는 통로 역할을 하는 Messenger입니다.

  1. Messenger의 경우 코드에서 mServiceCallback를 말하고 IBinder를 매개변수로 생성합니다.

  2. Messenger의 경우 코드에서 mClientCallback를 말하고 Handler를 매개변수로 생성합니다.

  3. Messenger는 Service의 Callback 객체이고, 2) Messenger는 Service가 Activity를 Callback할 수 있는 Callback 객체라고 생각하면 쉬울 것 같습니다.

예를 들어 Activity => Service로 이벤트를 보내고 Service가 모든 작업을 처리하고 결과를 Service=>Activity로 이벤트를 보내고 싶을 때가 있는데요.

  1. Messenger를 사용하여 이벤트와 2) Messenger 객체를 보내면, Service는 모든 작업을 처리하고 Activity로 부터 받은 2) Messenger를 이용하여 Activity로 이벤트를 보낼 수 있습니다.

Activity => Service로 이벤트 전달

위의 코드에서 onServiceConnected가 callback되었을 때 Activity는 Service에 Connect되었다는 Event를 Service에 보내게 됩니다. event는 MSG_CLIENT_CONNECT이고, Service가 Activity를 Callback할 수 있도록 replyTo에 mClientCallback를 넣어줍니다.

Service => Activity로 이벤트 전달

Service의 Messenger는 MSG_CLIENT_CONNECT라는 이벤트를 받아 Handler로 이벤트를 보내줍니다. 아래 코드처럼 Log가 찍히고, Client의 Messenger를 관리하는 mClientCallbacks에 추가하였습니다.

이제 Service는 MainActivity의 Callback 객체를 알고 있어서, MainActivity가 replyTo에 Messenger객체를 주지 않아도 스스로 MainActvity에 이벤트를 보낼 수 있습니다.

case MSG_CLIENT_CONNECT:
    Log.d(TAG, "Received MSG_CLIENT_CONNECT message from client");
    mClientCallbacks.add(msg.replyTo);
    break;

Activity => Service => MainActivity로 이벤트 전달

비슷하게 Service로 이벤트를 전달하고 다시 MainActivity로 결과를 보내주도록 해보겠습니다. 아래 코드처럼 버튼을 누르면 MSGADDVALUE 이벤트로 arg1=10을 넣고 Service로 보냅니다.

        Button btnAddValue = (Button) findViewById(R.id.btn_add_value);
        btnAddValue.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mServiceCallback != null) {
                    // request 'add value' to service
                    Message add_msg = Message.obtain(null, MsgService.MSG_ADD_VALUE);
                    add_msg.arg1 = 10;
                    add_msg.replyTo = mClientCallback;
                    try {
                        mServiceCallback.send(add_msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    Log.d(TAG, "Send MSG_ADD_VALUE message to Service");
                }
            }
        });

Service는 아래 코드처럼 10을 더하고 그 결과 값을 Activity를 포함한 모든 Client에 전달해줍니다.

case MsgService.MSG_ADDED_VALUE:
                    Log.d(TAG, "Recevied MSG_ADDED_VALUE message from service ~ value :" + msg.arg1);
                    break;

참고

  • 예제코드는 GitHub에 있습니다