Android(安卓)跨进程通信基础

Android跨进程通信基础——Binder, BinderProxy, parcel, parcelable, Stub, Stub.Proxy(该文章最早于2013年6月7日发表于有道云笔记进入阅读)


百度、google 过很多文章,都没能找到能够从 API 使用者角度简单描述 Binder,BinderProxy,Parcel,Parcelable,Stub,Stub.Proxy 之间关系的文章,要么高深莫测,要么混乱不清。最终决定还是自己动手,看源码,看文档,现总结如下:


Binder,BinderProxy 形成了进程间通信的基础,相当于公路桥梁;

Parcel 在 IBinder 基础上传输数据,相当于运输工具;

Parcelable基本数据类型和实现了 Parcelable接口的复合数据类型才可被 Parcel 传输,相当于摆放整齐、安检合格的货物;

Stub,Stub.Proxy 实现跨进程调用的接口,相当于收发货方。


注:Binder,BinderProxy 都实现了IBinder接口


下面以 Activity 与 Service 通信为例来分析其运行机制。

示例目的:通过跨进程调用方式在Activity 进程端调用 Service进程端的方法。这些方法由接口RemoteSSO 定义。这里假设两者是运行在不同进程的(也可以运行在相同进程,具体因配置而不同)。


代码实现

1、定义RemoteSSO.aidl 文件:

packagecom.sina.sso;interfaceRemoteSSO{StringgetPackageName();StringgetActivityName();}

2、ADT会在gen目录下生成RemoteSSO.java文件如下:

/**Thisfileisauto-generated.DONOTMODIFY.*Originalfile:D:\\workspace_android\\IeltsPraBook\\src\\com\\sina\\sso\\RemoteSSO.aidl*/packagecom.sina.sso;publicinterfaceRemoteSSOextendsandroid.os.IInterface{/**Local-sideIPCimplementationstubclass.*/publicstaticabstractclassStubextendsandroid.os.Binderimplementscom.sina.sso.RemoteSSO{privatestaticfinaljava.lang.StringDESCRIPTOR="com.sina.sso.RemoteSSO";/**Constructthestubatattachittotheinterface.*/publicStub(){this.attachInterface(this,DESCRIPTOR);}/***CastanIBinderobjectintoancom.sina.sso.RemoteSSOinterface,*generatingaproxyifneeded.*/publicstaticcom.sina.sso.RemoteSSOasInterface(android.os.IBinderobj){if((obj==null)){returnnull;}android.os.IInterfaceiin=obj.queryLocalInterface(DESCRIPTOR);if(((iin!=null)&&(iininstanceofcom.sina.sso.RemoteSSO))){return((com.sina.sso.RemoteSSO)iin);}returnnewcom.sina.sso.RemoteSSO.Stub.Proxy(obj);}@Overridepublicandroid.os.IBinderasBinder(){returnthis;}@OverridepublicbooleanonTransact(intcode,android.os.Parceldata,android.os.Parcelreply,intflags)throwsandroid.os.RemoteException{switch(code){caseINTERFACE_TRANSACTION:{reply.writeString(DESCRIPTOR);returntrue;}caseTRANSACTION_getPackageName:{data.enforceInterface(DESCRIPTOR);java.lang.String_result=this.getPackageName();reply.writeNoException();reply.writeString(_result);returntrue;}caseTRANSACTION_getActivityName:{data.enforceInterface(DESCRIPTOR);java.lang.String_result=this.getActivityName();reply.writeNoException();reply.writeString(_result);returntrue;}}returnsuper.onTransact(code,data,reply,flags);}privatestaticclassProxyimplementscom.sina.sso.RemoteSSO{privateandroid.os.IBindermRemote;Proxy(android.os.IBinderremote){mRemote=remote;}@Overridepublicandroid.os.IBinderasBinder(){returnmRemote;}publicjava.lang.StringgetInterfaceDescriptor(){returnDESCRIPTOR;}@Overridepublicjava.lang.StringgetPackageName()throwsandroid.os.RemoteException{android.os.Parcel_data=android.os.Parcel.obtain();android.os.Parcel_reply=android.os.Parcel.obtain();java.lang.String_result;try{_data.writeInterfaceToken(DESCRIPTOR);mRemote.transact(Stub.TRANSACTION_getPackageName,_data,_reply,0);_reply.readException();_result=_reply.readString();}finally{_reply.recycle();_data.recycle();}return_result;}@Overridepublicjava.lang.StringgetActivityName()throwsandroid.os.RemoteException{android.os.Parcel_data=android.os.Parcel.obtain();android.os.Parcel_reply=android.os.Parcel.obtain();java.lang.String_result;try{_data.writeInterfaceToken(DESCRIPTOR);mRemote.transact(Stub.TRANSACTION_getActivityName,_data,_reply,0);_reply.readException();_result=_reply.readString();}finally{_reply.recycle();_data.recycle();}return_result;}}staticfinalintTRANSACTION_getPackageName=(android.os.IBinder.FIRST_CALL_TRANSACTION+0);staticfinalintTRANSACTION_getActivityName=(android.os.IBinder.FIRST_CALL_TRANSACTION+1);}publicjava.lang.StringgetPackageName()throwsandroid.os.RemoteException;publicjava.lang.StringgetActivityName()throwsandroid.os.RemoteException;}

3、定义MyRemoteSSOextendsRemoteSSO.Stub。由于RemoteSSO.Stub 是抽象类,我们应该继承该类并实现相应的功能。这里是以下两方法:

publicjava.lang.StringgetPackageName()throwsandroid.os.RemoteException;publicjava.lang.StringgetActivityName()throwsandroid.os.RemoteException;

4、在 Service 的onBind() 方法里面返回MyRemoteSSO 对象。

5、在 Activity 中调用 bindService(),并在连接后通过以下转换就可以与另一个进程中的 Service 通信,调用相关方法了。

ServiceConnectionmServConnection=newServiceConnection(){@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderbinder){RemoteSSOmInterface=RemoteSSO.Stub.asInterface(binder);...mInterface.getPackageName();mInterface.getActivityName();...}}

至此,通道构造完毕,可以进行跨进程通信了。


运行机制

1、绑定 Service,建立跨进程通信通道

a、Activity 中调用 context.bindService(),Android 通过系统服务来启动或者连接到 Service;

b、系统服务通知Service 回调onBind()方法返回 Binder 对象的一个实例(Binder 对象事实上是RemoteSSO.Stub 的子类对象,而RemoteSSO.Stub 继承自 Binder);

c、系统服务通知 Activity 进程端生成一个对应的BinderProxy 对象,并作为onServiceConnected(ComponentName name, IBinder binder)方法的回调参数binder,此时我们需要将binder 作为参数手动调用 RemoteSSO.Stub.asInterface(binder) 来获取一个实现了 RemoteSSO接口的代理对象 proxy 以供我们调用,此时通道建立完成,我们可以通过proxy来调用 Service 端的方法了。

由于proxy 是实现了自定义接口 RemoteSSO 的,因此外部看来只是调用了接口方法而不是代理。事实上,proxy 是一个 RemoteSSO.Stub.Proxy 对象的实例,参数binder(BinderProxy对象实例)便作为了proxy的一个变量备用。

Binder 和 BinderProxy是成对的,在进程间用 descriptor 来标记对应关系,descriptor 通常是 RemoteSSO 的全路径名,在自动生成的 RemoteSSO.java 接口中已经自动生成。同时Stub 和Stub.Proxy 也是成对的,Service 端返回的是继承了Binder的Stub 的子类对象实例,而Activity端用来实现调用的接口实例是将BinderProxy作为变量的Stub.Proxy 对象的实例。


2、调用与返回

毕竟,我们的最终目的是通信,这里就是调用方法。那么当我们拿到 RemoteSSO 接口的实例 proxy,调用getPackageName() 的时候,执行过程是怎样的呢?

a、由于proxy 是 RemoteSSO.Stub.Proxy 对象的实例,则 proxy.getPackageName() 便是 RemoteSSO.Stub.Proxy的getPackageName()。

@Overridepublicjava.lang.StringgetPackageName()throwsandroid.os.RemoteException{android.os.Parcel_data=android.os.Parcel.obtain();android.os.Parcel_reply=android.os.Parcel.obtain();java.lang.String_result;try{_data.writeInterfaceToken(DESCRIPTOR);mRemote.transact(Stub.TRANSACTION_getPackageName,_data,_reply,0);_reply.readException();_result=_reply.readString();}finally{_reply.recycle();_data.recycle();}return_result;}

该代码是自动生成的。注意这里的mRemote 就是BinderProxy。


b、代理对象 RemoteSSO.Stub.Proxy将方法的参数打包到 Parcel 对象的实例_data 里,并由BinderProxy 调用transact()(见mRemote.transact(Stub.TRANSACTION_getPackageName, _data, _reply, 0);)通过底层系统级跨进程机制通知 Service 端的Binder(Service.onBind() 返回的RemoteSSO.Stub 子类对象)调用 transact(int code, Parcel data, Parcel reply,int flags);注意方法名是通过code参数标记的,见常量Stub.TRANSACTION_getPackageName。

c、Binder.transact() 回调RemoteSSO.Stub 的onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),进而调用自定义接口方法,并将返回值写入到reply 进行打包并返回。这里是getPackageName(),该自定义方法是手动在RemoteSSO.Stub 的子类中实现的(具体要做什么只有自己知道,ADT 不可能帮你生成)。


@OverridepublicbooleanonTransact(intcode,android.os.Parceldata,android.os.Parcelreply,intflags)throwsandroid.os.RemoteException{switch(code){caseINTERFACE_TRANSACTION:{reply.writeString(DESCRIPTOR);returntrue;}caseTRANSACTION_getPackageName:{data.enforceInterface(DESCRIPTOR);java.lang.String_result=this.getPackageName();reply.writeNoException();reply.writeString(_result);returntrue;}caseTRANSACTION_getActivityName:{data.enforceInterface(DESCRIPTOR);java.lang.String_result=this.getActivityName();reply.writeNoException();reply.writeString(_result);returntrue;}}returnsuper.onTransact(code,data,reply,flags);}

注意这里根据code 对方法名的标记找到对应的实际要调用的方法。


d、Binder.transact()、BinderProxy.transact() 一层层返回,即RemoteSSO.Stub.Proxy.getPackageName() 方法的mRemote.transact(Stub.TRANSACTION_getPackageName, _data, _reply, 0) 方法返回,此时_reply 已经在 Service 进程中被填充了返回值,接下来可以读取该值并返回了。


至此,整个调用过程结束。


FAQ

大家有没有想过,如果调用发生在同一个进程会发生什么?

publicvoidonServiceConnected(ComponentNamename,IBinderbinder){RemoteSSOmInterface=RemoteSSO.Stub.asInterface(binder);...}publicstaticcom.sina.sso.RemoteSSOasInterface(android.os.IBinderobj){if((obj==null)){returnnull;}android.os.IInterfaceiin=obj.queryLocalInterface(DESCRIPTOR);if(((iin!=null)&&(iininstanceofcom.sina.sso.RemoteSSO))){return((com.sina.sso.RemoteSSO)iin);}returnnewcom.sina.sso.RemoteSSO.Stub.Proxy(obj);}

事实上,在同一个进程中onServiceConnected() 的参数binder 是 Binder 对象而不是 BinderProxy 对象,因此在执行asInterface() 的时候返回的是 binder 本身(见obj.queryLocalInterface(DESCRIPTOR);),也就是RemoteSSO.Stub 子类对象,而不是RemoteSSO.Stub.Proxy 对象,此时在执行getPackageName() 时直接就是调用手动实现的该方法,也就是直接调用了Service 端的该方法,而没有绕一个大圈。如果必然是在同一个进程,可以直接这样处理来获得调用接口:

publicvoidonServiceConnected(ComponentNamename,IBinderbinder){RemoteSSOmInterface=(RemoteSSO)binder;...}