在大多数人的印象中,在Android中用Binder机制进行跨进程通讯,Binder会在onTransact方法中处理Binder驱动发送过来的消息,这个方法会运行在Binder驱动所管理的Binder线程池中(Binder线程的创建和销毁是在用户空间,但是管理是由Binder驱动代为管理的)。
其实这么说是不对的,片面的,在大多数情况下,onTransact()方法的确是在Binder线程中处理Binder驱动发送过来的消息。除了一种情况,在两个进程中都存在Binder实体对象,相互发送远程请求的时候。
对Android的Binder机制有一定了解的朋友一定都知道,在一次跨进程通讯过程中主要涉及到四种类型的Binder。Server端持有Binder的本地对象,在本地的Binder创建的时候,在Binder驱动中会生成一个Binder实体对象,对应Server端的本地对象。在Client端向Server端发送请求的时候,我们会创建一个Binder代理对象,同样的Binder驱动会为我们创建一个Binder引用对象。
下面就用实际的例子为大家演示上面所说的特殊情况。实例通过AIDL来实现跨进程通讯(AIDL基于Binder,简化了Binder跨进程r通讯的实现。)
首先我们创建两个AIDL文件。如下图所示:
IBinder类型的对象是可以用于跨进程通讯传输的(在Android中应用程序所在的进程,和AMS进行通讯,就是传递了一个本地的Binder对象(即ApplicationThread)过去方便AMS与应用程序进行通讯)。下面编译我们的应用程序。
系统为我们生成了两个java文件,把这两个文件copy到我们的包下,方便对它们进行修改。然后删除我们的AIDL文件,重新编译项目。
现在,我们再创建一个Service组件MyService,注意在注册MyService组件时,要设置它的
android:process=".remote"属性值,指明它要运行在remote进程中,这样当Activity绑定它的时候,AMS发现MyService将要运行的进程不存在,便会向Zygote进程请求它创建一个新的进程以便我们的MyService运行。我们在MyService中创建一个本地的Binder,然后在onBind方法中返回它。
MyService主要代码如下:
private IMyServerAidlInterface.Stub serverBinder = new IMyServerAidlInterface.Stub() {
@Override
public String getServerStr(IBinder binder) throws RemoteException {
Log.v("MyServiceServer",Thread.currentThread().getName());
IClientAidlInterface clientBinder = IClientAidlInterface.Stub.asInterface(binder);
clientBinder.doToastString("skt");
return "你好";
}
};
@Override
public IBinder onBind(Intent intent) {
return serverBinder;
}
在Activity启动的时候绑定我们的MyService,同时在onServiceConnected回调中传入我们的本地Binder到服务端。主要代码如下。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MyService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
IMyServerAidlInterface serverBinder = IMyServerAidlInterface.Stub.asInterface(iBinder);
try {
String serverStr = serverBinder.getServerStr(clientBinder);
Log.v("MyServiceClient", Thread.currentThread().getName());
Log.v("MyServiceClient",serverStr);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
}
private IClientAidlInterface.Stub clientBinder = new IClientAidlInterface.Stub() {
@Override
public void doToastString(String str) throws RemoteException {
Log.v("MyServiceClient", Thread.currentThread().getName());
Log.v("MyServiceClient", str);
}
};
同样在上面复制的两个java文件中添加一些log打印。
下面执行我们的应用程序,log打印如下:
我们可以清楚的看到两次跨进程通讯请求都得到了正确的结果,但是在Activity中的Binder的onTransact方法和我们的实现的接口方法,确确实实的运行在了主线程,而不是我们的Binder线程。服务端的Binder回调是在我们的Binder线程中的。
那为什么会导致这样的情况呢?
其实这是Binder为我们做的小优化。大家想,我们在onServiceConnected的方法中,通过serverBinder向服务端请求通信(AIDL的实现默认同步,transact方法的flag参数默认为0。),因为我们的onServiceConnected是运行在主线程的。所以主线程暂时被挂起,等待远程通信的响应。
服务端收到Client端的请求后,执行onTransact方法然后调用getServerStr()方法,这两个方法是运行在服务端Binder线程中的,没毛病。然后我们在getServerStr()中通过我们传过来的Binder代理,向客户端发起一个远程通信,按理说当客户端收到这个请求后,会在客户端的Binder线程中处理。其实不然,我们要知道现在客户端的主线程是被挂起的,它没事干,闲人一个,在等待我们服务端的返回,我们在处理客户端的远程请求时,还要向客户端发起远程请求,所以默认的,服务端的Binder线程同样会被阻塞,等待客户端的结果返回,客户端也是得不到回复的,在这样的情况下,客户端的主线程会被用来处理这次远程通信的请求(即向服务端发起远程通信请求的所在线程)。如果我们在客户端的子线程,向服务端发送远程通信请求,那么客户端的子线程会被拿来处理服务端向客户端发起的远程请求,因为你没事干啊,闲人一个。
修改Activity中的代码:
在执行我们的应用程序。log打印如下:
这回客户端的Binder的方法回调执行在了子线程,即我们启动远程服务所在的线程。
那如果我们的主线程或者说我们请求远程服务的请求不给阻塞会出现什么的结果呢?
那客户端的Binder回调当然执行在客户端的Binder线程咯,下面我们来验证一下。
修改copy的IMyServerAidlInterface文件,找到它的transact方法,修改最后一个参数值为FLAG_ONEWAY。
mRemote.transact(Stub.TRANSACTION_getServerStr, _data, _reply, FLAG_ONEWAY);
表示不需要关心服务端的回复。这样我们请求远程服务的线程就不会被阻塞。我们在运行我们的程序。打印如下:
可以看到我们客户端的Binder回调在客户端的Binder线程中。
同理,只要我们如果在Server端的Binder回调,开启一个异步线程,或者切换到主线中去向客户端发起一个远程请求,或者把它请求客户端的transact方法的flag参数改成 FLAG_ONEWAY,那样我们客户端的Binder回调肯定是我们客户端的Binder线程池中的。大家可以去验证一下。
转载标明原文地址哦:https://blog.csdn.net/braintt/article/details/88046162
参考文章:https://blog.csdn.net/universus/article/details/6211589
参看书籍:《Android内核剖析》《Android系统情景源码分析》