アンドロイド - Messengerを利用して、Remote Service実装する

By JS | Last updated: March 05, 2018

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を利用して、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にあります

Related Posts

codechachaCopyright ©2019 codechacha