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

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

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

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

이 글에서는 Messenger class를 이용하여 Activity와 Service가 통신하는 샘플앱을 구현해보겠습니다.

샘플앱은 다음과 같은 동작을 합니다.

  1. Activity는 Service에 이벤트를 전달
  2. Service는 이벤트를 받고 어떤 동작을 수행
  3. Service는 Activity로 결과 이벤트를 전달

예제코드는 GitHub에서 확인할 수 있습니다.

Messenger를 이용하여 RPC를 구현

액티비티와 서비스가 다른 프로세스에서 실행되었을 때, 이 두개의 객체가 서로 메시지를 주고 받으려면 RPC를 구현해야 합니다. Messenger는 RPC를 쉽게 구현할 수 있도록 도와줍니다.

Messenger 객체를 생성하는데 필요한 것은 Binder와 Handler입니다. Binder를 이용하여 다른 Process와 binding하며 다른 Process에서 보낸 event는 Handler로 전달됩니다.

먼저 Service와 Activity를 구현하고, Messenger를 이용하여 두개의 Component들이 서로 통신할 수 있도록 만들겠습니다.

Remote Service 구현

아래 코드는 Remote Service를 구현한 코드입니다.

이 클래스는 Service를 상속하고 멤버변수로 Messenger 객체를 저장하는 mClientCallbacks를 갖고 있습니다. mClientCallbacks리스트의 Messenger 객체들은, 서비스에 바인딩하는 Activity 등의 객체들이 전달해주는 것입니다. 서비스가 어떤 작업을 수행하고 그 결과 값을 전달해줄 때 이 Messenger 객체를 통해서 이벤트를 전달할 수 있습니다. (우리는 1개의 액티비티만 서비스에 바인딩할 것이기 때문에 리스트에는 1개의 객체만 추가됩니다)

반면에, 멤버변수 mMessenger는 액티비티가 서비스에 이벤트를 전달할 때 사용하는 객체입니다. 액티비티가 서비스에 바인딩할 때, 서비스는 mMessenger 객체의 IBinder를 전달해 줍니다. 액티비티가 서비스로 전달한 이벤트들은 CallbackHandler 내부에서 처리가 됩니다.

public class RemoteService extends Service{
    private final String TAG = "RemoteService";
    public static final int MSG_CLIENT_CONNECT = 1;
    public static final int MSG_CLIENT_DISCONNECT = 2;
    public static final int MSG_ADD_VALUE = 3;
    public static final int MSG_ADDED_VALUE = 4;

    private ArrayList<Messenger> mClientCallbacks = new ArrayList<Messenger>();
    final Messenger mMessenger = new Messenger(new CallbackHandler());
    int mValue = 0;

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

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }

    @Override
    public void onDestroy(){
        super.onDestroy();
    }

    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, RemoteService.MSG_ADDED_VALUE);
                            added_msg.arg1 = mValue;
                            mClientCallbacks.get(i).send(added_msg);
                        }
                        catch( RemoteException e){
                            mClientCallbacks.remove( i );
                        }
                    }
                    break;
            }
        }
    }
}

Messenger를 통해 액티비티로부터 이벤트를 받을 때, Messenger는 서비스에 구현된 CallbackHandler로 이벤트를 던저준다고 생각하시면 좋습니다. 그래서 우리가 구현해야할 것은 Handler에 이벤트를 추가하는 것 뿐입니다.

Remote Service 설정

동일한 앱에서 Activity와 Service가 다른 프로세스에서 실행되게 만드려면 AndroidManifest.xml에서 서비스를 remote로 동작하도록 설정해줘야 합니다.

Remote Service로 실행되게 하려면, AndroidManifest.xml에서 내가 만든 Service를 정의하고 android:process 속성을 Remote Service로 설정하여야 합니다.

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

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

Activity 구현

Activity는 멤버변수로 두개의 Messenger 객체를 갖고 있습니다.

  • "mServiceCallback"은 서비스로부터 전달받는 객체입니다. 위의 서비스 코드에서 바인딩할 때 던져주는 IBinder로 만들어진 Messenger 객체입니다.
  • "mClientCallback"는 서비스로 전달해주는 객체입니다. 서비스에서 액티비티로 결과를 리턴하기 위해 사용되는 Messenger 객체입니다.

mClientCallback도 마찬가지로 CallbackHandler 객체를 생성자에 전달합니다. 서비스가 액티비티로 이벤트를 전달하면 Messenger 객체는 이벤트를 CallbackHandler에 던져줍니다.

public class MainActivity extends AppCompatActivity {
    private final String TAG = "MainActivity";

    private Messenger mServiceCallback = null;
    private Messenger mClientCallback = new Messenger(new CallbackHandler());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        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 msg = Message.obtain(
                        null, RemoteService.MSG_ADD_VALUE);
                    msg.arg1 = 10;
                    try {
                        mServiceCallback.send(msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    Log.d(TAG, "Send MSG_ADD_VALUE message to Service");
                }
            }
        });

        Log.d(TAG, "Trying to connect to service");
        Intent intent = new Intent(getApplicationContext(), RemoteService.class);
        bindService(intent, 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, RemoteService.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;
        }
    };

    private class CallbackHandler extends Handler
    {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case RemoteService.MSG_ADDED_VALUE:
                    Log.d(TAG, "Recevied MSG_ADDED_VALUE message from service ~ value :" + msg.arg1);
                    break;
            }
        }
    }
}

좀 더 자세한 설명

위의 코드를 부분적으로 나누어서 설명하겠습니다.

Activity에서 Service 바인딩

아래처럼 ServiceConnection을 정의하고 bindService를 통해 Service에 bind합니다. 바인딩이 되면 ServiceConnection으로 IBinder 객체가 전달되고 그것으로 Messenger 객체를 만들 수 있습니다.

사실 Messenger 객체는 서비스로부터 받은 것은 아니지만, 서비스로부터 받은 IBinder로 만든 Messenger 객체입니다. 이 객체로 서비스에 이벤트를 전달할 수 있습니다. (그냥 서비스로부터 받은 Messenger객체라고 생각하시는게 이해하기에는 편합니다..)

아직 액티비티는 서비스에게 Messenger객체를 전달하지 않았습니다. 전달해주는 코드는 onServiceConnected()에 있습니다. 바인딩으로 서비스로부터 Messenger객체를 받았고, 그 객체를 통해서 액티비티의 Messenger객체를 서비스에 전달하게 만들었습니다.

protected void onCreate(Bundle savedInstanceState) {
  ...
  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;
  }
};

액티비티로부터 전달된 Messenger 객체

액티비티는 서비스로 Messenger객체를 전달하였습니다. 아래 코드는 서비스의 CallbackHandler입니다. Messenger는 MSG_CLIENT_CONNECT 이벤트를 CallbackHandler로 던져줍니다. Handler 코드를 보시면 Messenger 객체를 받아서 mClientCallbacks 리스트에 추가하고 있습니다.

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;
    ...

Activity <=> Service 간의 이벤트 전달

지금까지 액티비티와 서비스의 Messenger 객체를 서로 교환하는 코드를 살펴보았습니다. Messenger의 도움으로 두개의 Component는 이제 서로에게 이벤트를 전달할 수 있습니다.

이제 일(Job)을 시켜보죠.

저는 샘플앱에서 액티비티가 서비스로 숫자를 전달하고, 서비스에서 누적된 숫자를 액티비티로 다시 리턴하는 내용을 구현하였습니다.

다음은 액티비티의 버튼이 눌렸을 때 동작하는 코드입니다. 버튼이 눌리면 숫자 10과 함께 MSG_ADD_VALUE 이벤트를 서비스로 전달합니다.

        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;
                    try {
                        mServiceCallback.send(add_msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    Log.d(TAG, "Send MSG_ADD_VALUE message to Service");
                }
            }
        });

다음은 서비스에서 MSG_ADD_VALUE 이벤트를 받는 코드입니다. 숫자를 받으면 멤버변수 mValue에 누적하고, 누적된 값을 액티비티로 전달합니다. 코드를 보시면 모든 callback 객체들에게 결과를 전달하고 있습니다. 바인딩된 액티비티가 10개라면 10개의 액티비티에 이벤트를 모두 전달하게 됩니다.

private class CallbackHandler  extends Handler {
    @Override
    public void handleMessage( Message msg ){
        switch( msg.what ){
            ...
            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, RemoteService.MSG_ADDED_VALUE);
                        added_msg.arg1 = mValue;
                        mClientCallbacks.get(i).send(added_msg);
                    }
                    catch( RemoteException e){
                        mClientCallbacks.remove( i );
                    }
                }
                break;

실행 결과

다음은 앱을 실행하고 버튼을 3번 눌렀을 때 출력되는 로그입니다. MainActivity와 RemoteService의 PID가 다른 것을 볼 수 있습니다.

10-29 21:27:14.815  5279  5279 D MainActivity: Trying to connect to service
10-29 21:27:15.123  5279  5279 D MainActivity: onServiceConnected
10-29 21:27:15.124  5279  5279 D MainActivity: Send MSG_CLIENT_CONNECT message to Service
10-29 21:27:15.129  5312  5312 D RemoteService: Received MSG_CLIENT_CONNECT message from client
10-29 21:27:27.394  5279  5279 D MainActivity: Send MSG_ADD_VALUE message to Service
10-29 21:27:27.394  5312  5312 D RemoteService: Received message from client: MSG_ADD_VALUE
10-29 21:27:27.394  5312  5312 D RemoteService: Send MSG_ADDED_VALUE message to client
10-29 21:27:27.402  5279  5279 D MainActivity: Recevied MSG_ADDED_VALUE message from service ~ value :10

10-29 21:27:52.284  5279  5279 D MainActivity: Send MSG_ADD_VALUE message to Service
10-29 21:27:52.285  5312  5312 D RemoteService: Received message from client: MSG_ADD_VALUE
10-29 21:27:52.285  5312  5312 D RemoteService: Send MSG_ADDED_VALUE message to client
10-29 21:27:52.297  5279  5279 D MainActivity: Recevied MSG_ADDED_VALUE message from service ~ value :20

10-29 21:27:53.623  5279  5279 D MainActivity: Send MSG_ADD_VALUE message to Service
10-29 21:27:53.624  5312  5312 D RemoteService: Received message from client: MSG_ADD_VALUE
10-29 21:27:53.624  5312  5312 D RemoteService: Send MSG_ADDED_VALUE message to client
10-29 21:27:53.630  5279  5279 D MainActivity: Recevied MSG_ADDED_VALUE message from service ~ value :30

정리

지금까지 Messenger를 이용하여 두개 이상의 Component가 서로 이벤트를 주고 받는 RPC를 구현하였습니다. Messenger를 이용하는 이유는 RPC 구현을 매우 쉽게 할 수 있기 때문입니다. (AIDL으로 구현하는게 더 편하다고 느끼실 수 있겠지만..)

포인트는 두개의 Component가 서로 Messenger 객체를 교환해야 한다는 것입니다. 서로 상대방으로 이벤트를 전달할 Messenger객체를 갖고 있으면 Handler에 이벤트를 구현해주는 것만 작성하면 더 구현할 것이 없습니다.

참고

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

Related Posts

codechachaCopyright ©2019 codechacha