前言
在我们的印象中,服务端Binder收到请求后调用onTransact处理消息,而运行的线程处于Binder管理的线程池汇中(Binder线程的创建和销毁是在用户空间,但是管理是由Binder驱动代为管理的)。这样说在大部分情况下正确,但也不是所以的情况均是如此。比如一种“远程回调”的情况,客户端代理在调用服务端方法后,也通过参数传递的方式传递一个IBinder给服务端,服务端获客户端取代理后回调。这种模式在源码中其实也常见。下面新建一个工程演示这种情况,顺便回顾一下AIDL的用法。
AIDL
创建AIDL文件
为了演示双向调用的情况,所以我们需要建立两个AIDL文件。这儿建了一个IHelloService和IHiService。
// IHelloService.aidl
package com.android.aidl;
// Declare any non-default types here with import statements
interface IHelloService {
void Hello(String words,IBinder client);
}
// IHiService.aidl
package com.android.aidl;
// Declare any non-default types here with import statements
interface IHiService {
void Hi(String words);
}
分别定义了两个接口,Hello和Hi,注意Hello接口的参数还要接收一个IBinder对象。这里多嘴说一下,虽然IBinder本质上是一个interface,没有真正意义上的IBinder对象。但是实际传递的是一个实现了IBinder接口的Binder对象,所以就这样简称了。这个对象就是让服务端收到请求后调用asInterface将其转为proxy后调用客户端的Hi方法。build后分别生成对应的java文件。以IHelloService.java为例:
build生成java文件
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.android.aidl;
// Declare any non-default types here with import statements
public interface IHelloService extends android.os.IInterface
{
/** Default implementation for IHelloService. */
public static class Default implements com.android.aidl.IHelloService
{
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.android.aidl.IHelloService
{
private static final java.lang.String DESCRIPTOR = "com.android.aidl.IHelloService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.android.aidl.IHelloService interface,
* generating a proxy if needed.
*/
public static com.android.aidl.IHelloService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.android.aidl.IHelloService))) {
return ((com.android.aidl.IHelloService)iin);
}
return new com.android.aidl.IHelloService.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@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 INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_Hello:
{
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
android.os.IBinder _arg1;
_arg1 = data.readStrongBinder();
this.Hello(_arg0, _arg1);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.android.aidl.IHelloService
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(words);
_data.writeStrongBinder(client);
boolean _status = mRemote.transact(Stub.TRANSACTION_Hello, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().Hello(words, client);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.android.aidl.IHelloService sDefaultImpl;
}
static final int TRANSACTION_Hello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.android.aidl.IHelloService impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.android.aidl.IHelloService getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException;
}
IHelloService.Stub是服务端需要继承的类,通过调用asInterface可以将IBinder转为生成IHello.Stub.Proxy客户端对象。AIDL文件build后生成的java文件有一些标志是默认的,为了方便之后修改观察现象,我们将AIDL删除,然后把build后的java文件加入工程。
创建Service
为了使用Binder,我们还需要创建一个Service组件来完成IBinder对象的传递。
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new HelloService();
}
class HelloService extends IHelloService.Stub{
@Override
public void Hello(String words, IBinder client) throws RemoteException {
Log.d("HelloService",words +" thread:" + Thread.currentThread());
(IHiService.Stub.asInterface(client)).Hi("Hi");
}
}
}
如上所示,service的创建非常简单,定义HelloService服务类并实现IHelloService.Stub的Hello方法,只需要在onBind方法中new 一个HelloService实体然后返回即可。我们在服务端中将收到的IBinder对象client通过IHiService.Stub.asInterface转化为IHiService的proxy然后就可以调用客户端提供的服务接口Hi了。
创建MainActivity
Activity的创建也非常简单,创建HiService类,然后调用bindService去请求服务,同时把一个HiService的实例传递过去即可,如下所示。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(this, MyService.class), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
(IHelloService.Stub.asInterface(service)).Hello("hello",new HiService());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
},BIND_AUTO_CREATE);
}
class HiService extends IHiService.Stub{
@Override
public void Hi(String words) throws RemoteException {
Log.d("HiService",words +" thread:" + Thread.currentThread());
}
}
}
结果分析
客户端和服务端同一进程
现在运行该程序看一下打印。
com.android.aidl D: hello , thread:Thread[main,5,main]
com.android.aidl D: Hi thread:Thread[main,5,main]
看来相互调用是成功了,但是都是在主线程执行的?这也对,他们在同一进程是不需要真正binder驱动参与的。那么将Service改到单独进程执行看看。
不同进程,主线程请求
要让service执行在不同的进程,只需要指定process属性即可。
<service android:name=".MyService"
android:process=":remote"/>
下面运行看看结果:
u0_a36 27264 326 1579504 70660 0 0000000000 S com.android.aidl:remote
从ps的结果来看service的确执行在了新进程。log打印的结果如下
27264-27279/com.android.aidl D: hello , thread:Thread[Binder:27264_3,5,main]
27247-27247/com.android.aidl D: Hi thread:Thread[main,5,main]
从打印结果来看service端的比较符合预期,执行在了binder线程中,但是为什么MainActivity端还是在主线程?这就是在本文一开头所说的特殊情况,这是Binder帮我们做的小优化,因为在MainActivity远程调用service的过程中本来就是阻塞等调用完成,没事儿干,那么发起远端请求的线程就会被用来执行这次服务。因为就算Binder开一个线程去执行这次服务,由于service端在等待返回,主线程仍是阻塞的,所以根本不需要重新开个线程执行。我们可以大胆猜测如果在MainActivity的子线程发起这样的请求,服务也应该在子线程处理,下面改一下代码。
不同进程,子线程发起请求
修改代码,子线程发起请求。
@Override
public void onServiceConnected(ComponentName name, final IBinder service) {
new Thread(new Runnable() {
@Override
public void run() {
try {
(IHelloService.Stub.asInterface(service)).Hello("hello",new HiService());
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
下面看一下执行结果。
29014-29030/com.android.aidl D: hello , thread:Thread[Binder:29014_3,5,main]
28995-29057/com.android.aidl D: Hi thread:Thread[Thread-3,5,main]
果然,这次Hiservice服务的执行就在创建的子线程中了,所以结论与上面分析的一致。
不同进程,修改transact发送标志
刚才说到因为阻塞所以导致HiService就在向HelloService发送请求的线程中执行,那么如果不阻塞的情况呢?因为transact是有flag参数的,其中有个flag是FLAG_ONEWAY,表示单向通信,不在乎结果。记得最开始我们删掉AIDL文件,也是为了这一步骤,可以修改stub里面的东西。我们试试将HelloService的transact改为
@Override public void Hello(java.lang.String words, android.os.IBinder client) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(words);
_data.writeStrongBinder(client);
boolean _status = mRemote.transact(Stub.TRANSACTION_Hello, _data, _reply, FLAG_ONEWAY);
运行查看log
29309-29321/com.android.aidl D: hello , thread:Thread[Binder:29309_1,5,main]
29293-29306/com.android.aidl D: Hi thread:Thread[Binder:29293_2,5,main]
这次两边都在Binder线程中执行了。同理,只修改HiService端transact的标志结果也是在Binder线程中执行。
总结
总的来说,Binder服务的执行会尽量放在Binder线程池的线程中去执行,减少对用户本来的线程的影响,当然会有一些特殊情况,那么Binder也会灵活地找到相应线程去执行,减少不必要的开销。
