Android Service IPC通信之Messenger机制

【Android Service IPC通信之Messenger机制】敢说敢作敢为, 无怨无恨无悔。这篇文章主要讲述Android Service IPC通信之Messenger机制相关的知识,希望能为你提供帮助。
概述 之前我写过一篇博客介绍Binder: Android Binder机制完全解析, 里面讲过如何实现Service的跨进程( IPC) 通信, 主要是通过编写AIDL接口文件来实现的。本篇我们来讲讲Service IPC通信的另外一种方式—Messenger
Messenger, 也称为信使, 通过它可以在不同的进程间传递message对象, 在message中放入我们需要传递的数据你就可以实现跨进程通信和传递数据了。所以说Messenger机制是基于消息的跨进程通信方式。

Android Service IPC通信之Messenger机制

文章图片

可以看到, 我们可以在客户端发送一个Message给服务端, 在服务端的handler中会接收到客户端的消息, 然后进行相应的处理, 处理完成后, 再将结果等数据封装成Message, 发送给客户端, 客户端的handler中会接收到处理的结果。
客户端和服务端可相互持有对方的Messenger来进行通信, 除此之外, 服务端还可以记录客户端对象的Messenger, 然后实现一对多的通信; 甚至作为一个转接处, 任意两个进程都能通过服务端进行通信, 这个后面再说。
实例演示 下面我们通过一个例子来演示一下Messenger机制的用法, 客户端发送一条消息给服务端, 提供两个参数和一个运算符, 服务端完成两个参数的特定运算, 并通过消息将运算结果发回给客户端, 客户端再显示出来。
我们来看看具体的实现。在Studio下创建两个Module, 分别为客户端和服务端:
Android Service IPC通信之Messenger机制

文章图片

先看看服务端代码:
public class MessengerService extends Service { private static final int MSG_SUM = 0x110; //最好换成HandlerThread的形式 private Messenger mSeviceMessenger = new Messenger(new Handler() { @ Override public void handleMessage(Message msgfromClient) { Message msgToClient = Message.obtain(msgfromClient); //返回给客户端的消息 switch (msgfromClient.what) { //msg 客户端传来的消息 case MSG_SUM: msgToClient.what = MSG_SUM; try { //模拟耗时 Thread.sleep(2000); msgToClient.arg2 = msgfromClient.arg1 + msgfromClient.arg2; msgfromClient.replyTo.send(msgToClient); } catch (InterruptedException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } break; } super.handleMessage(msgfromClient); } }); @ Override public IBinder onBind(Intent intent) { return mSeviceMessenger.getBinder(); } }

服务端就一个Service, 代码相当的简单, 只需要去声明一个服务端Messenger对象, 然后在onBind方法返回mSeviceMessenger.getBinder();
然后就坐等客户端将消息发送到handleMessage了, 根据message.what去判断进行什么操作, 然后做相应的操作( 这里只实现了相加运算) , 最终将结果通过 msgfromClient.replyTo.send(msgToClient); 发回给客户端。
可以看出这里是取出客户端传来的两个数字, 然后求和返回, 这里我有意添加了Thread.sleep(2000); 模拟耗时, 注意在实际使用过程中, 应该换成在独立开辟的线程中完成耗时操作( 否则耗时过长会引起服务端ANR) , 比如和HandlerThread结合使用。
public class MessengerService extends Service { private HandlerThread mThread; private Handler mHandler; private Messenger mSeviceMessenger; private static final int MSG_SUM = 0x110; @ Override public void onCreate() { super.onCreate(); initBackThread(); }private void initBackThread() { mThread = new HandlerThread(" MessengerService" ); mThread.start(); //注意这里Handler中传入的是HandlerThread的Looper mHandler = new Handler(mThread.getLooper()) { @ Override public void handleMessage(Message msgfromClient) { Message msgToClient = Message.obtain(msgfromClient); //返回给客户端的消息 switch (msgfromClient.what) { //msg 客户端传来的消息 case MSG_SUM: msgToClient.what = MSG_SUM; try { //模拟耗时 Thread.sleep(2000); msgToClient.arg2 = msgfromClient.arg1 + msgfromClient.arg2; msgfromClient.replyTo.send(msgToClient); } catch (InterruptedException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } break; } super.handleMessage(msgfromClient); } }; mSeviceMessenger = new Messenger(mHandler); }@ Override public IBinder onBind(Intent intent) { return mSeviceMessenger.getBinder(); } }

别忘了在Manifest里注册service:
< service android:name= " .MessengerService" android:enabled= " true" android:exported= " true" > < intent-filter> < action android:name= " com.hx.messenger" /> < category android:name= " android.intent.category.DEFAULT" /> < /intent-filter> < /service>

接下来看客户端代码
布局文件:
< LinearLayout android:id= " @ + id/id_ll_container" xmlns:android= " http://schemas.android.com/apk/res/android" xmlns:tools= " http://schemas.android.com/tools" android:layout_width= " match_parent" android:layout_height= " match_parent" android:orientation= " vertical" android:paddingBottom= " @ dimen/activity_vertical_margin" android:paddingLeft= " @ dimen/activity_horizontal_margin" android:paddingRight= " @ dimen/activity_horizontal_margin" android:paddingTop= " @ dimen/activity_vertical_margin" tools:context= " .MainActivity" > < TextView android:id= " @ + id/id_tv_callback" android:layout_width= " match_parent" android:layout_height= " wrap_content" android:text= " Messenger Test!" /> < Button android:id= " @ + id/id_btn_add" android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:text= " add" /> < /LinearLayout>

思路是这样的, 最上面的TextView显示当前和远程Service的连接状态。Button按钮每点击一次就会生成两个整形参数, 用一个TextView显示出来动态添加到LinearLayout, 并且将这两个参数通过message发送给服务端, 待服务端返回结果后将结果也显示到对应的TextView上。
public class MainActivity extends AppCompatActivity { private Button mBtnAdd; private LinearLayout mContainer; private TextView mTvState; //显示连接状态private Messenger mServiceMessenger; private boolean isConn; private static final int MSG_SUM = 0x110; private int mNum = 0; //相加运算第一个参数private Messenger mClientMessenger = new Messenger(new Handler() { @ Override public void handleMessage(Message msgFromServer) { switch (msgFromServer.what) { case MSG_SUM: TextView tv = (TextView) mContainer.findViewById(msgFromServer.arg1); //根据Id找到对应的TextView, 防止错乱 tv.setText(tv.getText() + " = " + msgFromServer.arg2); break; } super.handleMessage(msgFromServer); } }); private ServiceConnection mConn = new ServiceConnection() { @ Override public void onServiceConnected(ComponentName name, IBinder service) { mServiceMessenger = new Messenger(service); isConn = true; mTvState.setText(" connected!" ); }@ Override public void onServiceDisconnected(ComponentName name) { mServiceMessenger = null; isConn = false; mTvState.setText(" disconnected!" ); } }; @ Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //开始绑定服务 bindServiceInvoked(); mTvState = (TextView) findViewById(R.id.id_tv_callback); mBtnAdd = (Button) findViewById(R.id.id_btn_add); mContainer = (LinearLayout) findViewById(R.id.id_ll_container); mBtnAdd.setOnClickListener(new View.OnClickListener() { @ Override public void onClick(View v) { try { int a = mNum+ + ; int b = (int) (Math.random() * 100); //创建一个TextView,添加到LinearLayout中 TextView tv = new TextView(MainActivity.this); tv.setText(a + " + " + b); tv.setId(a); //这里给TextView赋一个Id, 之后可以从mContainer中通过Id找到这个TextView mContainer.addView(tv); Message msgFromClient = Message.obtain(null, MSG_SUM, a, b); msgFromClient.replyTo = mClientMessenger; if (isConn) { //往服务端发送消息 mServiceMessenger.send(msgFromClient); } } catch (RemoteException e) { e.printStackTrace(); } } }); }private void bindServiceInvoked() { Intent intent = new Intent(); intent.setAction(" com.hx.messenger" ); bindService(intent, mConn, Context.BIND_AUTO_CREATE); }@ Override protected void onDestroy() { super.onDestroy(); unbindService(mConn); } }

首先bindService, 然后在onServiceConnected中拿到回调的service( IBinder) 对象, 通过service对象去构造一个服务端Messenger 对象, 然后就可以使用mServiceMessenger.send(msg)发送消息给服务端了。服务端会收到消息, 处理完成后会将结果返回, 传到客户端的mClientMessenger中的Handler的handleMessage方法中。
Android Service IPC通信之Messenger机制

文章图片

实际上, 通过Messenger来传输Message, Message中能使用的载体只有what, arg1, arg2, Bundle和replyTo这几种。Message的另一个字段object在同一进程中很实用, 但在IPC通信时, 2.2之前不支持, 2.2之后也仅仅是系统提供的实现了Parcelable接口的对象才能通过它传输。所幸还有bundle, bundle中支持大量数据类型。
Bundle bundle = new Bundle(); bundle.putString(" msg" , " hello" ); msgFromClient.setData(bundle);

接收端:
msgfromClient.getData().getString(" msg" );

注: 若你使用的是android 5.0版本以上的设备, 运行客户端程序会崩溃, 控制台报如下错误: java.lang.IllegalArgumentException: Service Intent must be explicit
原因: 在android 5.0版本( Lollipop) 以后, service intent必须为显式指出
这里有两种解决方法
  • 设置intent的packageName
在bindService之前, 调用intent.setPackage(packagename); 方法, packagename为定义service所在的包名。此方式是google官方推荐使用的解决方法。
Intent intent = new Intent(); intent.setAction(" com.hx.messenger" ); intent.setPackage(" com.hx.messenger_server.MessengerService" ); bindService(intent, mConn, Context.BIND_AUTO_CREATE);

实验证明这种方式只适用于调用同进程的Service, 而不适用于调用远程Service。所以不能用于本例的情况。
  • 将隐式启动转换为显示启动
public static Intent getExplicitIntent(Context context, Intent implicitIntent) { // Retrieve all services that can match the given intent PackageManager pm = context.getPackageManager(); List< ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); // Make sure only one match was found if (resolveInfo = = null || resolveInfo.size() != 1) { return null; } // Get component info and create ComponentName ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); // Create a new intent. Use the old one for extras and such reuse Intent explicitIntent = new Intent(implicitIntent); // Set the component to be explicit explicitIntent.setComponent(component); return explicitIntent; }//调用方式如下 private void bindServiceInvoked() { Intent intent = new Intent(); intent.setAction(" com.hx.messenger" ); //for android 5.0 and later, service intent must be explicit Intent eintent = new Intent(getExplicitIntent(this,intent)); bindService(eintent, mConn, Context.BIND_AUTO_CREATE); }

本例可以使用这种方法解决。
同时运行一下服务端和客户端程序, 多次点击ADD按钮, 效果如下:
Android Service IPC通信之Messenger机制

文章图片

可以看到, 我们每点击一次按钮, 就往服务器发送一条消息, 服务器拿到消息执行完成后, 将结果返回。
这里值得说明的一点是, 大家通过代码可以看到服务端往客户端传递数据是通过msg.replyTo这个对象的。那么服务端完全可以做到, 使用一个List甚至Map去存储所有绑定的客户端的msg.replyTo对象, 然后想给谁发消息都可以。甚至可以把A进程发来的消息, 通过B进程的msg.replyTo发到B进程那里去, 从而实现任意两个进程都能通过服务端进行通信。
源码分析 其实Messenger的内部实现也是依赖于aidl文件的。
( 1) 首先我们看客户端向服务端通信
服务端:
服务端的onBind是这么写的:
public IBinder onBind(Intent intent) { return mMessenger.getBinder(); }

看看getBinder()方法
public IBinder getBinder() { return mTarget.asBinder(); }

可以看到返回的是mTarget.asBinder(); mTarget是什么呢? 别忘了我们前面去构造mServiceMessenger对象的代码: new Messenger(new Handler());
public Messenger(Handler target) { mTarget = target.getIMessenger(); }

原来是Handler返回的, 我们继续跟进去
final IMessenger getIMessenger() { synchronized (mQueue) { if (mMessenger != null) { return mMessenger; } mMessenger = new MessengerImpl(); return mMessenger; } }private final class MessengerImpl extends IMessenger.Stub { public void send(Message msg) { msg.sendingUid = Binder.getCallingUid(); Handler.this.sendMessage(msg); } }

mTarget是一个MessengerImpl对象, 那么asBinder实际上是返回this, 也就是MessengerImpl对象。这是个内部类, 可以看到继承自IMessenger.Stub, 然后实现了一个send方法, 该方法就是将接收到的消息通过 Handler.this.sendMessage(msg); 发送到handleMessage方法。
看到这, 大家有没有想到什么, 难道不觉得extends IMessenger.Stub这种写法异常的熟悉么? 我们传统写aidl文件, aapt给我们生成什么, 生成IXXX.Stub类, 然后我们服务端继承IXXX.Stub实现接口中的方法。没错, 其实这里内部其实也是依赖一个aidl生成的类, 这个aidl位于: frameworks/base/core/Java/android/os/IMessenger.aidl
package android.os; import android.os.Message; /** @ hide */ oneway interface IMessenger { void send(in Message msg); }

看到这, 你应该明白了, Messenger并没有什么神奇之处, 实际上, 就是依赖该aidl文件生成的类, 继承了IMessenger.Stub类, 实现了send方法, send方法中参数会通过客户端传递过来, 最终发送给handler进行处理。
客户端:
客户端首先通过onServiceConnected拿到sevice( Ibinder) 对象, 这里没什么特殊的, 我们平时的写法也是这样的, 只不过我们平时会这么写:
Service mService = IMessenger.Stub.asInterface(service);

而我们的代码中是
mServiceMessenger = new Messenger(service);

跟进去, 你会发现:
public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }

原来和我们平时的写法一模一样!
到这里就可以明白, 客户端与服务端通信, 实际上和我们平时的写法没有任何区别, 通过编写aidl文件, 服务端onBind利用Stub编写接口实现返回; 客户端利用回调得到的IBinder对象, 使用IMessenger.Stub.asInterface(target)拿到接口实例进行调用。
( 2) 服务端与客户端通信
客户端与服务端通信的确没什么特殊的地方, 我们完全也可以编写个类似的aidl文件实现; 那么服务端是如何与客户端通信的呢?
还记得, 客户端send方法发送的是一个Message, 这个Message.replyTo指向的是一个mClientMessenger, 我们是在Activity中初始化的。那么将消息发送到服务端, 肯定是通过序列化与反序列化拿到Message对象, 我们看下Message的反序列化的代码:
# Message private void readFromParcel(Parcel source) { what = source.readInt(); arg1 = source.readInt(); arg2 = source.readInt(); if (source.readInt() != 0) { obj = source.readParcelable(getClass().getClassLoader()); } when = source.readLong(); data = source.readBundle(); replyTo = Messenger.readMessengerOrNullFromParcel(source); sendingUid = source.readInt(); }

主要看replyTo, 调用的是Messenger.readMessengerOrNullFromParcel(source);
public static Messenger readMessengerOrNullFromParcel(Parcel in) { IBinder b = in.readStrongBinder(); return b != null ? new Messenger(b) : null; }public static void writeMessengerOrNullToParcel(Messenger messenger, Parcel out) { out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder() : null); }

通过上面的writeMessengerOrNullToParcel可以看到, 它将客户端的messenger.mTarget.asBinder()对象进行了恢复, 客户端的message.mTarget.asBinder()是什么?
客户端也是通过Handler创建的Messenger, 于是asBinder返回的是:
public Messenger(Handler target) { mTarget = target.getIMessenger(); }final IMessenger getIMessenger() { synchronized (mQueue) { if (mMessenger != null) { return mMessenger; } mMessenger = new MessengerImpl(); return mMessenger; } }private final class MessengerImpl extends IMessenger.Stub { public void send(Message msg) { msg.sendingUid = Binder.getCallingUid(); Handler.this.sendMessage(msg); } }public IBinder getBinder() { return mTarget.asBinder(); }

那么asBinder, 实际上就是MessengerImpl extends IMessenger.Stub中的asBinder了。
#IMessenger.Stub @ Override public android.os.IBinder asBinder() { return this; }

那么其实返回的就是MessengerImpl对象自己。到这里可以看到message.mTarget.asBinder()其实返回的是客户端的MessengerImpl对象。
最终, 发送给客户端的代码是这么写的:
msgfromClient.replyTo.send(msgToClient);

public void send(Message message) throws RemoteException { mTarget.send(message); }

这个mTarget实际上就是对客户端的MessengerImpl对象的封装, 那么send(message)( 屏蔽了transact/onTransact的细节) , 这个message最终肯定传到客户端的handler的handleMessage方法中。
好了, 到此我们的源码分析就结束了~~
总结下:
  • 客户端与服务端通信, 利用的aidl文件, 没什么特殊的
  • 服务端与客户端通信, 主要是在传输的消息上做了处理, 让Messager.replyTo指向的客户端的Messenger,而Messenger又持有客户端的一个Binder对象( MessengerImpl) 。服务端正是利用这个Binder对象做到与客户端的通信。
Demo下载地址

    推荐阅读