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を別のプロセスとして登録していますが、まだ同じアプリケーションです。
このサービスの実現は簡単です。
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には以下のように登録されています。
ActivityシミュレーションCientプロセスを作成します。
このActivityはデフォルトでこのアプリのプロセスです。具体的には以下のようになります。
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を取得してから、コードは以下の通りです。
またMessengerはどのようにメッセージを送るかを見てください。即ちMessengerのsend方法です。
また、上記の例では、プロセス間で基本的な値を伝達するだけで、実際には単一プロセスのようなメッセージメカニズムも、Bundleデータを伝えることができますが、順序付けが必要です。具体的な説明は、Messageソースの基本フィールドサポートを参照してください。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。
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ソースの基本フィールドサポートを参照してください。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。