2013/02/11

Android:Messengerの基本

プロセス間の双方向通信をサポートするサービスにはAIDLとMessengerが存在します。

AIDLとMessengerの主な違いは下記になります。

●AIDL
  • サービスホスト(以降ホスト)は.aidlを作成し、サービスクライアント(以降クライアント)はこれを取り込む必要がある
  • クライアントからのリクエストはBinderThread経由で、全てのリクエストが非同期通信となる
  • ホストは必要に応じてクライアントからのリクエストをスレッドセーフに扱う必要がある
  • .aidlに変更があった場合、クライアントも合わせてこれを更新する必要がある

●Messenger
  • ホストとクライアント間の通信はHandler-Messageの仕組みで実現されているため.aidlが不要
  • クライアントからのリクエストはHandler経由で通知され、全てのリクエストが同期通信となる
  • ホストとクライアント間のメッセージはMessageオブジェクトで表現される

Messengerはクライアントからのリクエストをシングルスレッドで処理するため、基本的にスレッドセーフで動作します。
また、メッセージがMessageオブジェクトで表現されるため.aidlファイルも不要です。
外部公開APIに変更があっても、.aidlファイルをクライアントに配布する必要がないため、
サービス側でMessageの規約(プロトコル)を更新するだけで済みます(互換性を意識する必要があります)

Messengerの難点として、"外部公開APIの直観性の低さ"があります。
AIDLでは外部公開APIはメソッドとして定義されます。
しかし、Messengerでは"Messageオブジェクトの組み立て方"でこれを表現する必要があります。
クライアントはMessengerと通信するためにメッセージの組み立て方を理解する必要があるのです。
(アプリローカルで使用されるHandler-Messageのそれと同じです)


●サンプルコード
Messengerを使用した双方向通信のスニペットです。

・クライアントサイド
public class MessengerClient extends Activity {
    private Messenger mServiceMessenger;
    private Messenger mSelfMessenger;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mSelfMessenger = new Messenger(new ResponseHandler());
        mConnection = new ServiceConnection() {
            ...
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mServiceMessenger = new Messenger(service);
            }
        };
        bindService(new Intent("yuki.test.messenger.START"),
                mConnection, Service.BIND_AUTO_CREATE);

        ((Button)findViewById(R.id.button1)).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mServiceMessenger != null) {
                            try {
                                Message msg = Message.obtain(null, 1);
                                msg.replyTo = mSelfMessenger;
                                mServiceMessenger.send(msg);
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
    }

    private class ResponseHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.e("yuki", "handle response=" + msg);
        }
    }
}

・ホストサイド
public class MessengerService extends Service {
    private Messenger mServiceMessenger;

    @Override
    public void onCreate() {
        super.onCreate();
        mServiceMessenger = new Messenger(new RequestHandler());
    }

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

    private class RequestHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.e("yuki", "handle request=" + msg);

            if (msg.replyTo != null) {
                try {
                    msg.replyTo.send(Message.obtain()); // send response.
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

・クライアント⇒ホストへの通信
ホストはMessenger.getBinder()で得られるBinderをonBindコールバックで返します。
クライアントはonServiceConnectedコールバックで得られるBinderインスタンスをMessengerコンストラクタに
指定してMessengerオブジェクトを生成し、サービスとの通信路を確立します。
Messengerオブジェクトにメッセージをsendすればホストに届きます。

・ホスト⇒クライアントへの通信
クライアントはホストに渡すMessageオブジェクトのreplayToフィールドに自身のMessengerを設定します。
ホストはクライアントからのMessageオブジェクトからこれを取得し、クライアント⇒ホスト時と同様にメッセージを送信します。

・通信相手の不在
Messengerの対象となっているオブジェクト(クライアント or ホスト)が既にDeadObject化している場合はDeadObjectExceptionが投げられます。
クライアントもホストも通信相手はリモートプロセスとなるため、メッセージの送信にはRemoteExceptionのcatchが必要です。


●リクエストメッセージのキューイングによる処理遅延
Messengerはシングルスレッドモデルであるため、クライアントからのリクエストを逐次処理していきます。
※handleMessageはメインスレッド上で動作するのがデフォルトです
1つのリクエスト処理に時間が掛かると、次のリクエストを処理するのが遅れます。
これはつまり、ホストからの応答性が悪くなる可能性があるということです。
ホストとクライアントが1:多の関係にあり、リクエストの処理に手間取った場合この問題は顕著になります。

クライアントからのリクエスト処理(handleMessage)がメインスレッドで実行されることに注目します。
リクエスト処理で遅延が発生すると、onBindやonDestroy等他のメインスレッドで実行される処理にも影響が出ます。
サービスバインド要求等のリクエストも、それ以前にキューイングされたメッセージ"全て"が処理されてからでないと実行されません。

そのため、メインスレッド上でのリクエスト処理に手間取ると、
  •  bindServiceを実行しても中々サービスコネクトされない
  •  unbindServiceしたが、中々サービスがDestroyされない
といったことが起こりえます。

Messengerはシングルスレッドモデルです。
クライアントからのバインドやアンバインド要求もリクエストメッセージとして処理されるため、スレッドセーフが保証されます。
ホスト側のHandlerを別のHandlerThread(Looper)と紐付けたものをonBindで返せば、onBindやonDestroyが即実行されるようになりますが、 Messengerのメリットであるシングルスレッドモデルを崩すことになります。

・不在のクライアントからのリクエスト
クライアントからのリクエストはメッセージキューにキューイングされます。
クライアントがリクエストを送信し、ホスト側でメッセージキューからデキューされる間にクライアント側がいなくなるケースがあります。
クライアントが不在となっても、一度キューイングされたメッセージはホストに届きます。