一、引言
关于Android
中的进程间通信,我们知道大概可以通过以下方式进行:
-
Bundle
:四大组件间通信 -
File
:文件共享 -
ContentProvider
:应用间数据共享 -
AIDL
:Binder
机制 -
Messager
:基于AIDL
、Handler
实现 -
Socket
:建立C/S
通信模型
本文主要探索的是AIDL
和Socket
两种实现方式,并在日常使用的基础上根据AIDL
所生成的代码分析 Binder
跨进程通信机制,感兴趣的童鞋可以看看。
本文完整代码:AndroidIPCDemo
二、使用 AIDL
和 Socket
进行通信
先来说说我们一会儿要实现的通信模型,大致如下图:
需要实现的通信模型然后看看目录结构:
文件路径
再看看IMyAidlInterface.aidl
,这里在定义方法名称的时候需要注意的是方法不能同名,包需要手动导入,可能是因为AIDL
文件在解析时不会区分参数类型,导致我在设定同名方法时一直编译错误,搞得我一直找其他问题,所以这点需要注意一下:
// IMyAidlInterface.aidl
package com.project.horselai.bindprogressguarddemo;
// 需要手动导入的包
import com.project.horselai.bindprogressguarddemo.IMyAidlInterfaceCallback;
import com.project.horselai.bindprogressguarddemo.MyMessage;
interface IMyAidlInterface {
void sendMessage(String msg);
void sendMessageObj(in MyMessage msg);
int getProcessId();
void registerCallback(IMyAidlInterfaceCallback callback);
void unregisterCallback(IMyAidlInterfaceCallback callback);
}
然后是IMyAidlInterfaceCallback.aidl
:
// IMyAidlInterfaceCallback.aidl
package com.project.horselai.bindprogressguarddemo;
interface IMyAidlInterfaceCallback {
void onValueCallback(int value);
}
最后是 MyMessage.aidl
:
// MyMessage.aidl
package com.project.horselai.bindprogressguarddemo;
parcelable MyMessage;
其他代码太长就不贴出来了,具体请查看 AndroidIPCDemo。
演示图如下,具体还是跑起来看看吧。
演示.gif 日志输出信息这里有个现象就是,unbindService
调用之后,ServiceConnection
并没有中断,因此,如果此时再次发送消息也是能够发送和接收到的。
三、从AIDL
生成类源码角度理解Binder
机制
1. 先来点关于 IBinder 的理论知识
官方原文:IBinder
IBinder
作为远程对象的基础接口,是为高性能执行跨进程和进程内调用设计的轻量级远程调用机制的核心部分,它描述了与远程对象交互时的抽象协议,使用时应该继承 Binder
,而应直接实现 IBinder
接口。
IBinder
的关键 API
是 transact()
,它与 Binder.onTranscat()
配对使用,当调用transcat()
方法时会发送请求到IBinder
对象,而接收到请求时是在Binder.onTranscat()
中接收,transcat()
是同步执行的,执行transcat()
后transcat()
会等待对方Binder.onTranscat()
方法返回后才返回,这种行为在同一进程中执行时是必然的,而在不同进程间执行时,底层IPC
机制也会确保具备与之相同的行为。
transact()
方法发送的是Parcel
类型的数据,Parcel
是一种通用数据缓冲,它包含一些描述它所承载内容的元数据(meta-data
),这些元数据用于管理缓冲数据中的IBinder
对象引用,因此这些引用可以被保存为缓冲数据而传递到其他进程。这种机制保证了IBinder
能够被写入Parcel
中并发送到其他进程,如果其他进程发送相同的IBinder
引用回来给源进程,则说明源进程收到一个相同的IBinder
对象,这种特性使IBinder/Binder
对象能够作为进程间的唯一标识(作为服务器token
或者其他目的)。
系统会为每个运行的进程维护一个事务线程池,线程池中的线程用于分发所有来自其他进程的IPC
事务,例如,当进程A
与进程B
进行IPC
时(此时A
为发送进程),由于A
调用transact()
发送事务到进程B
的缘故,A
中被调用的线程会被阻塞在transact()
,此时如果B
进程中的可用线程池线程接收到了来自A
的事务,就会调用目标对象(A
进程)的Binder.onTranscat()
,并回复一个Parcel
作为应答。接收到来自B
进程的应答后,在A
进程中执行transact()
的线程就会结束阻塞,从而继续执行其他逻辑。
Binder
系统同样支持跨进程递归,例如,如果进程A
执行一个事务到进程B
,然后进程B
处理接收到的事务时又执行了由进程A
实现的IBinder.transact()
,那么进程A
中正在等待原事务执行结束的线程将用于执行由进程B
调用的对象的Binder.onTranscat()
应答。这种行为保证了递归机制在远程调用Binder
对象和本地调用时行为一致。
通过以下三种方式可以确定远程对象是否可用:
- 当调用一个不存在进程的
IBinder.transact()
时会抛出RemoteException
异常; - 调用
pingBinder()
返回false
时表示远程进程已经不存在; - 使用
linkToDeath()
方法给IBinder
注册一个IBinder.DeathRecipient
,那么当其承载进程被杀死时会通过这个监听器通知;
2. AIDL 生成类源码分析
先来看看生成类的结构:
生成类的结构其中IMyAidlInterface
接口是我们定义AIDL
接口的直接代码生成,而IMyAidlInterface.Stub
则是实现IMyAidlInterface
接口的抽象类,实现了onTranscat()
方法,不过它并没有具体实现IMyAidlInterface
的方法,而是将这部分的实现交给了IMyAidlInterface.Stub.Proxy
。
OK,我们来具体分析一下。首先定位到Stub#asInterface
,可见它主要负责区分当前进行的是本地通信还是跨进程通信。
public static com.project.horselai.bindprogressguarddemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
// 1. 查找本地是否存在这个 IBinder 对象
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.project.horselai.bindprogressguarddemo.IMyAidlInterface))) {
// 如果是本地通信,则稍后进行本地通信
return ((com.project.horselai.bindprogressguarddemo.IMyAidlInterface) iin);
}
// 2. 否则,稍后使用这个对象进行远程通信
return new com.project.horselai.bindprogressguarddemo.IMyAidlInterface.Stub.Proxy(obj);
}
接着来看看Stub#onTranscat
方法, 各参数作用如下
-
code: 标识需要执行的动作,是一个从
FIRST_CALL_TRANSACTION
到LAST_CALL_TRANSACTION
之间的数字。 -
data:
transcat()
调用者发送过来的数据。 -
reply: 用于给
transcat()
调用者写入应答数据。 -
flags: 如果是
0
,代表是一个普通RPC
,如果是FLAG_ONEWAY
则代表是一个one-way
类型的RPC
。 -
return: 返回
true
代表请求成功了,返回false
则表示你没有明白事务代码(code
)。
基于前面的理论知识,我们已经知道进程A
中的onTransact()
会被进程B
调用,用于远程回调应答数据,下面通过两个标志性的方法解释在onTransact()
中都做了什么:
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
// 对于远程写请求
case TRANSACTION_sendMessage: {
data.enforceInterface(descriptor);
java.lang.String _arg0;
// 1. 从进程A的远程请求包中读取请求数据
_arg0 = data.readString();
// 2. 执行进程B中的sendMessage方法写入来自进程A的数据
this.sendMessage(_arg0);
reply.writeNoException();
return true;
}
// 对于远程读请求
case TRANSACTION_getProcessId: {
data.enforceInterface(descriptor);
// 1. 执行进程B中的getProcessId() 读取需要作为响应数据的数据
int _result = this.getProcessId();
// 2. 将读取到的响应数据写入到进程A的应答中
reply.writeNoException();
reply.writeInt(_result);
return true;
}
// ...
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
可能你会对上面的this.sendMessage(_arg0)
和this.getProcessId()
有所疑问,比如,为什么在TRANSACTION_sendMessage
中还要执行this.sendMessage(_arg0)
,这不就死循环了吗? 不会的,为啥呢,因为TRANSACTION_sendMessage
判断的是来自进程A
的方法类型码,而在解析了来自进程A
的请求参数data
后会调用进程B
自身的sendMessage(_arg0)
方法将数据保存到自己的存储内存中,而它的sendMessage(_arg0)
是有我们自己实现的,如下是我们在进程B
中的实现:
IMyAidlInterface.Stub myAidlInterface = new IMyAidlInterface.Stub() {
@Override
public void sendMessage(String msg) throws RemoteException {
Log.i(TAG, "sendMessage: " + msg);
}
@Override
public int getProcessId() throws RemoteException {
return Process.myPid();
}
};
到这是不是就很好理解了。
下面通过Proxy#sendMessage
和Proxy#getProcessId
两个与上面对应的方法来解释作为客户端的进程A
是如何给远程作为服务端的B
进程发送请求的:
@Override
public void sendMessage(java.lang.String msg) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain(); // 请求参数
android.os.Parcel _reply = android.os.Parcel.obtain(); // 响应数据
try {
// 1. 封装远程请求参数
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(msg);
// 2. 通过Binder执行远程请求,最终响应数据会封装在_reply
mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply, 0);
// 3. 没有需要返回数据则仅读取异常
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public int getProcessId() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain(); // 请求参数
android.os.Parcel _reply = android.os.Parcel.obtain(); // 响应数据
int _result;
try {
// 1. 没有参数,则仅写入标识
_data.writeInterfaceToken(DESCRIPTOR);
// 2. 通过Binder执行远程请求,最终响应数据会封装在_reply
mRemote.transact(Stub.TRANSACTION_getProcessId, _data, _reply, 0);
// 3. transact阻塞结束后读取响应数据
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
可见,实际上_reply
一直使用的都是同一个,由进程A
创建,发送给B
进程,进程B
会将处理好的响应数据写入到_reply
中,并最终通过onTranscat
方法回调给进程A
,这样就完成了一个RPC
。
总的来说,整个过程的执行流程如下:
使用Binder进行IPC
四、Messenger
使用与源码分析
1. 使用
在Service
进程中如下使用:
public class MessengerRemoteService extends Service {
private static final String TAG = "MessengerRemoteService";
private Messenger mMessenger;
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
// 3. 使用 Messenger 进行进程间通信
mMessenger = new Messenger(new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, "handleMessage: " + msg);
Log.i(TAG, "handleMessage data: " + msg.getData().get("msg"));
return true;
}
}));
}
}
然后在Activity
中如下建立服务连接:
// for Messenger
private Messenger mMessenger;
ServiceConnection mServiceConnection3 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected3: ");
mMessenger = new Messenger(service);
btnBindRemote.setEnabled(false);
mIsBond = true;
Toast.makeText(MainActivity.this, "service bond 3!", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected 3: ");
mIsBond = false;
btnBindRemote.setEnabled(true);
}
};
绑定后如下发送信息:
if (mMessenger != null){
Message message = new Message();
Bundle bundle = new Bundle();
bundle.putString("msg","message clicked from Main ..");
message.what = 122;
message.setData(bundle);
try {
mMessenger.send( message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
日志输出如下:
日志输出基于上面的使用,整个流程下来你会发现Messenger
的通信是单向的,如果想要双向的话,那么需要在作为客户端的进程A
上也创建一个Messenger
和Handler
,然后在B
进程中发送响应消息。
为了能够进行双向通信,我们可以对上面代码进行如下修改,其中MessengerRemoteService
中的Messenger
可以这么修改:
mMessenger = new Messenger(new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, "handleMessage: " + msg);
Log.i(TAG, "handleMessage data: " + msg.getData().get("msg"));
Message message = Message.obtain();
message.replyTo = mMessenger;
Bundle bundle = new Bundle();
bundle.putString("msg", "MSG from MessengerRemoteService..");
message.setData(bundle);
message.what = 124;
try {
// 注意这里
msg.replyTo.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
return true;
}
}));
注意到上面的msg.replyTo.send(message)
,其中msg.replyTo
是一个代表发送这个消息的Messenger
。在Activity
中可以这么改:
// onCreate中
mClientMessenger = new Messenger(mHandler);
// ...
Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 2) {
Toast.makeText(MainActivity.this, "" + msg.obj, Toast.LENGTH_SHORT).show();
return true;
}else if (msg.what == 124){
Toast.makeText(MainActivity.this, msg.getData().getString("msg"), Toast.LENGTH_SHORT).show();
Log.i(TAG, "handleMessage: " + msg.getData().getString("msg"));
Log.i(TAG, "handleMessage: ");
return true;
}
textView.setText(String.valueOf(msg.obj));
return true;
}
});
最后Activity
收到消息时会弹出收到的消息,如下图:
整个双向通信的流程如下:
Messenger双向通信2. Messenger 实现原理
Messenger底层仅仅是简单地包裹了一下Binder
,具体来说就是也使用的AIDL
,因此它不会影响到进程的生命周期,不过当进程销毁时,连接也会中断。
下面来简要看一下它的部分源码:
public final class Messenger implements Parcelable {
private final IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
// ...
}
其中IMessenger
是个AIDL
接口,如下:
package android.os;
import android.os.Message;
/** @hide */
oneway interface IMessenger {
void send(in Message msg);
}
有了前面的知识基础,这玩意儿就很好理解了。
五、总结
本文主要描述了Android
进程间通信中的AIDL
和Socket
两种方式,文中没有对Socket
方式做过多描述和分析,是因为使用Socket
通信是比较基础的事情,并且它的实现过程相对容易理解,因此就一笔带过了,具体实现源码请查看 AndroidIPCDemo。文中着重从AIDL
生成源码角度分析了Binder
的运行机制,并简单介绍了Messenger
的使用及其实现。
OK,水平有限,欢迎理性指正。
网友评论