Android Messengerはプロセス間通信とその原理を実現します。


前言
Androidメッセージのソースコードを分析していたが、replayTo、IMessinegerなどの属性フィールドに遭遇したことがあります。これらのフィールドはプロセス間通信に使われるというだけで、深く分析していませんでした。今日はこの文章でMessengerを使って、プロセス間通信をどのように行い、そのソースコードを分析して実現するかを実証します。
Messengerプロセス間通信の流れ
Messengerは名前の通り、つまりメッセンジャーです。その役割は違うプロセスの両方の通信需要を満たすことです。通常、私たちはAIDLを書いてプロセス間通信を実現しますが、簡単なIPCはMessengerで実現できます。知っておくべきはMessengerもAIDLに基づいています。Messengerがパッケージを作ってくれただけです。そのプロセス間通信の枠組みはこうです。

上記のように、2つのプロセスがそれぞれClient ProcessとServer Processであると仮定して、まずServer側が自分側のMessengerをClientに引用してClientに伝達し、ClientがServer端から送られてきたMessengerを使ってServer端にメッセージを送ることで、一方向通信を実現した。同じように、双方向通信を実現するためには、Cient側もServer側にMessengerを送る必要があります。Server側もこのMessengerを利用してCientにメッセージを送ることができます。MessengerはAIDLに基づいていますが、最下階はBinderに基づいています。
Messengerプロセス間の双方向通信例
ServiceシミュレーションServerプロセスを作成します。
一般的なプロセス間通信は二つのAppの間で行われることが多いが、一つのアプリにも複数のプロセスがあり、これは一般的で、アプリケーションのプッシュサービスのように単独のプロセスにある。もちろんこのServiceを別のアプリに作成してもいいですが、テストをしやすいように、ここではServiceを別のプロセスとして登録していますが、まだ同じアプリケーションです。
このサービスの実現は簡単です。

public class RemoteService extends Service {
    private WorkThread mWorkThread = new WorkThread();
    private Messenger mMessenger;

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

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

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

    private void prepareMessenger() {
        mMessenger = new Messenger(mWorkThread.mHandler);
    }

    private class WorkThread extends Thread {
        Handler mHandler;

        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case MessageConstant.CLIENT_TO_SERVER:
                            Toast.makeText(RemoteService.this, "Hello Server:" + msg.arg1 + "," + msg.arg2, Toast.LENGTH_SHORT).show();
                            if (msg.replyTo != null) {
                                try {
                                    msg.replyTo.send(Message.obtain(null, MessageConstant.SERVER_TO_CLIENT, 0, msg.arg1 + msg.arg2));
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }
                            }
                            break;
                        default:
                            break;
                    }

                }
            };
            prepareMessenger();
            Looper.loop();
        }

        public void quit() {
            mHandler.getLooper().quit();
        }
    }
上記のコードは簡単ですが、いくつか注意が必要です。
1、なぜServiceで作業スレッドを開くのですか?Serviceは4つのコンポーネントの一つとして、メインスレッドで実行されているので、時間のかかる操作ができません。プロセス間のインタラクションが時間のかかる操作であれば、Serviceのプロセスはブロックされます。Clientエンドプロセスはブロックされません。
2、このServiceでMessengerオブジェクトを作成し、オンラインでIBinderオブジェクトに戻りました。ここはプロセス間通信の鍵です。後で詳しく分析します。
3、このServiceのサブスレッドにHandlerを作成し、プロセス間通信のメッセージ処理にMessengerを関連付ける。Handlerメッセージ処理は私たちが普段使っているのと同じですが、サブスレッドはデフォルトのLooperがないので、自分で作成して起動する必要があります。そうしないと、サブスレッドのHandlerはMessageを受信できません。
4、Server端末がメッセージを受け取ったら、Toastは「hello server」とCentから送られてきた二つの整数値を表示します。Client側も自分のMessengerを送ったら、Client側にメッセージを返信して、二つの整数の和を返します。
また、このServiceのAndroid Manifest.xmlには以下のように登録されています。

<service
    android:name=".messenger.RemoteService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote">
    <intent-filter>
        <action android:name="com.aspook.remote.ACTION_BIND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>
コアの文はAndroid:process=「remote」で、このServiceを別のプロセスの中に置いて、同じアプリでプロセス間通信をシミュレートすることができます。
ActivityシミュレーションCientプロセスを作成します。
このActivityはデフォルトでこのアプリのプロセスです。具体的には以下のようになります。

/**
 * demo for IPC by Messenger
 */
public class MessengerActivity extends AppCompatActivity {

    private Button btn_start;
    private Button btn_bind;
    private Button btn_send;
    private boolean mBound = false;
    private Messenger mRemoteMessenger = null;

    private ServiceConnection mRemoteConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mRemoteMessenger = new Messenger(service);
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteMessenger = null;
            mBound = false;
        }
    };

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

        findViews();
        setListeners();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mRemoteConnection);
    }

    public void findViews() {
        btn_start = (Button) findViewById(R.id.btn_start);
        btn_bind = (Button) findViewById(R.id.btn_bind);
        btn_send = (Button) findViewById(R.id.btn_send);
    }

    public void setListeners() {
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // start Remote Service first
                Intent intent = new Intent(MessengerActivity.this, RemoteService.class);
                startService(intent);
                btn_start.setEnabled(false);
            }
        });

        btn_bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // bind the Remote Service, if the Remote service run in another App, you should run the App and start the service first
                try {
                    bindRemoteService();
                    btn_bind.setEnabled(false);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        btn_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mBound) {
                    Handler mClientHandler = new Handler() {
                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            switch (msg.what) {
                                case MessageConstant.SERVER_TO_CLIENT:
                                    Toast.makeText(MessengerActivity.this, "Hello Client:" + msg.arg2, Toast.LENGTH_SHORT).show();

                                    break;
                                default:
                                    break;
                            }
                        }
                    };

                    try {
                        Message msg = Message.obtain(null, MessageConstant.CLIENT_TO_SERVER, 66, 88);
                        // Messenger of client sended to server is used for sending message to client
                        msg.replyTo = new Messenger(mClientHandler);
                        mRemoteMessenger.send(msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(MessengerActivity.this, "Service not bind", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    /**
     * bind service
     */
    public void bindRemoteService() {
        // Method one
        Intent intent = new Intent("com.aspook.remote.ACTION_BIND");// 5.0+ need explicit intent
        intent.setPackage("com.aspook.androidnotes"); // the package name of Remote Service
        bindService(intent, mRemoteConnection, BIND_AUTO_CREATE);
    }
}
コードロジックも簡単です。画面には3つのボタンがあります。操作は以下の通りです。
1、先にServer端のServiceを起動して、とりあえずリモートサービスを起動するといいます。
2、リモートサービスをバインドする
3、ClientはServcie端末にメッセージを送信し、返信メッセージを受信する。
注意すべき点は以下の通りです。
1、リモートServiceをバインドした後、Client端末がServer端のMessengerから引用を受けました。
2、Client端のMessengerは自分のHandlerに関連してServerから受信したメッセージを処理する必要があります。ここでも注意したいのですが、Server端とClient端が相互作用するのも時間がかかるなら、サブスレッドを開く必要があります。この例ではメッセージを表示するだけで、UIスレッドに直接置くことができます。
3、双方向通信が必要なら、Client側はMessageのreplyToパラメータで自分のMessengerをServer端に送る必要があります。
4、Android 5.0+Serviceをバインディングするときは明示的なIntentを使用しなければならない。パッケージ名を設定することによって解決できます。私は同じアプリで開く二つのプロセスなので、パッケージ名は同じですが、リモートServiceが別のアプリにある場合は、その場所のAppのパッケージ名を記入するべきです。
5、Client側は返信メッセージを受け取った後、Toast「Hello client」と二つの整数の合計。
効果の例を示します
以上の例のプロセス間通信効果のデモは以下の通りです。

Messengerプロセス間の通信原理分析
Serviceの起動、バインディングについては言うまでもなく、まずClientからリモートServiceをバインドしてServer端のMessengerを取得してから、コードは以下の通りです。

private ServiceConnection mRemoteConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mRemoteMessenger = new Messenger(service);
        mBound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mRemoteMessenger = null;
        mBound = false;
    }
};
次にmRemoteMessenger=new Messengerを見にきます。ソースの実現:

/**
 * Create a Messenger from a raw IBinder, which had previously been
 * retrieved with {@link #getBinder}.
 * 
 * @param target The IBinder this Messenger should communicate with.
 */
public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}
この構造方法のパラメータIBinderは、リモートServiceのonBindから返ってきます。具体的なコードは以下の通りです。

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
}
このコードをもう一度見てください。

mTarget = IMessenger.Stub.asInterface(target);
mTargetはIMessinegerの対象で、見たところますますAIDLの書き方に似ています。似ているとは言えません。元々はAIDLです。ソースコードの中には必ずIMesenger.aidlというファイルがあります。メッセージを送るためのインタフェースを定義するべきです。やはりソースディレクトリの「/frame eweorks/base/core/java/android/os/」でIMessineger.aidlファイルを見つけました。その内容は以下の通りです。

package android.os;
import android.os.Message;

/** @hide */
oneway interface IMessenger {
    void send(in Message msg);
}
だから、MessengerはAIDLを書く作業を省いてくれただけです。下の階はやはりAIDLです。
またMessengerはどのようにメッセージを送るかを見てください。即ちMessengerのsend方法です。

/**
 * Send a Message to this Messenger's Handler.
 * 
 * @param message The Message to send.  Usually retrieved through
 * {@link Message#obtain() Message.obtain()}.
 * 
 * @throws RemoteException Throws DeadObjectException if the target
 * Handler no longer exists.
 */
public void send(Message message) throws RemoteException {
    mTarget.send(message);
}
コメントからわかるように、Messengerは関連するHandlerにメッセージを送ります。また、Handlerが存在しない場合は異常となります。これはクライアントを作成する時も、サーバーMessengerを作成する時もHandlerを作成した理由です。
また、上記の例では、プロセス間で基本的な値を伝達するだけで、実際には単一プロセスのようなメッセージメカニズムも、Bundleデータを伝えることができますが、順序付けが必要です。具体的な説明は、Messageソースの基本フィールドサポートを参照してください。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。