大家应该都使用过AIDL来对Service进行远程调用,大家可能都有过一些疑问,现在我来说说我对于AIDL的理解。
其实对于AIDL进行IPC通讯中,在我们自己写的实现了ServiceConnection接口中
public void onServiceConnected(ComponentName name, IBinder service) {
queryService = IStudentQuery.Stub.asInterface(service);
}
这个返回回来的IBinder类型的service其实是一个指针,或者在java中说是引用,它指向了ServiceManager中指向我们要调用的Service中的那个Binder binder = new StudentQuery.Stub()也就是我们在onBind中return的那个(Binder实现了IBinder接口)。(我之前在一个视频教程里听到说返回的是一个指向指针的指针,应该是指向指向了ServiceManager中的一个指针,而ServiceManager这个指针指向了Service中的binder,这个纯粹是我个人的见解,在这里你就可以理解为不管它到底是指向什么的,其实并没有把binder这个对象传过来,而是传过来一个指针。这样可以使内存信息更安全一些,使传输速率更快和内存占用更小)。
从Stub.asInterface(service)
public static cn.cc.aidl.StudentQuery asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof cn.cc.aidl.StudentQuery))) {
return ((cn.cc.aidl.StudentQuery)iin);
}
return new cn.cc.aidl.StudentQuery.Stub.Proxy(obj);
}
如果client和service在同一个进程,返回本来的service,不是则返回一个proxy,这个proxy实现了我们要调用的接口Proxy implements cn.cc.aidl.IStudentQuery而我们要调用接口中的方法时候,它就会调用传进来service的方法,这里mRemore指向了Proxy(obj)中的obj,Stub.TRANSACTION_query用来标识调用的是query这个方法(在这里返回一个proxy对象,里面只有对Binder的引用,不用实例化一个Binder,这样更加轻量级)
@Override public java.lang.String query(int number) throws android.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);
_data.writeInt(number);
mRemote.transact(Stub.TRANSACTION_query, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
mRemote是传递过来的binder指针,而它在service端实际是一个binder对象,因为Stub extends android.os.Binder implements cn.cc.aidl.StudentQuery,所以它调用transact在Binder的源码中
**
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
然后它又调用了boolean r = onTransact(code, data, reply, flags),而在Stub中重写了onTransact
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_query:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.query(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
所以我之前说的code是为了标识我们调用的是query这个方法,然后java.lang.String _result = this.query(_arg0),我们在这里会实现这个方法。
public class StudentQueryService extends StudentQuery.Stub{
@Override
public String query(int number) throws RemoteException {
return queryStudent(number);
}
}
通过这样的学习我们对Android的这个IPC框架有了一些了解,我们只要轻松的实现自己的接口,在Service端写好Stub,在poxy端调用就行了。
其实我今天写这个博客最开始是对我看过《Android开发艺术探索》中的一个内容产生了疑问,是这样的如果我们在AIDL中有一个传输的参数是AIDL,这个AIDL中的AIDL,传过去的是引用还是对象实例,比如这里我们有一个IManager.aidl,里面有两个方法registerListener(Listener listener)和unregisterListener(Listener listener)是我们向service注册观察者里面的Listener也是AIDL,当我们使用同一个listener先调用registerListener(Listener listener)(会向一个List里面加入一个listener),然后调用unregisterListener(Listener listener)(从List里面删除listenre)的时候却不能注销观察者,不是同一个对象,这里书的解释是
这里说是虽然在客户端使用同一个对象调用这两个方法,但是在服务端却会对AIDL里面传输的对象要进行反序列化,从而使两次的listener不一样。
这里我不太理解,因为传递一个AIDL的时候可以只传递引用,所以传递AIDL中的AIDL我认为也是传递引用。后面我看了一下源码,在Stup中
case TRANSACTION_registerListener:
{
data.enforceInterface(DESCRIPTOR);
cn.cc.aidl.IArrivedListener _arg0;
_arg0 = cn.cc.aidl.Listener.Stub.asInterface(data.readStrongBinder());
this.registerListener(_arg0);
reply.writeNoException();
return true;
}
这里Listener.Stub.asInterface(data.readStrongBinder())和之前分析的单个AIDL是一样的,所以可以更加确定AIDL中的AIDL也是传递的是一个引用。这里就会有疑问了,为什么调用两次方法传递的是同一个引用,却在服务端显示对象不一样呢?
首先从data里面binder的引用读出来,然后再asInterface把它保存到Proxy中,原因就在这里因为asInterface每次都会new Proxy,所以两次方法调用会有两个proxy对象,两个proxy对象里的mRemote都指向同一个binder。虽然都指向同一个binder,但是他们确实是两个对象,不能很好的unregisterListener(Listener listener)。
这本书中建议我们使用RemoteCallbackList来保存客户端传过来的binder,它使用registe(E callback)来向里面添加
/**
* Simple version of {@link RemoteCallbackList#register(E, Object)}
* that does not take a cookie object.
*/
public boolean register(E callback) {
return register(callback, null);
}
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
}
}
从源码中可以看到IBinder binder = callback.asBinder()调用我们传进去的proxy的asBinder()方法,而它返回的是proxy中的mRemote,然后mCallbacks.put(binder, cb)(ArrayMap<IBinder, Callback> mCallbacks使用的是map来保存,key为IBinder),所以同一个binder它只会保存一个,不管你有几个proxy。(也间接的解释了,服务端返回给我们的是同一个binder引用)
又看unregister(E callback)这个方法,它从List里面删除
public boolean unregister(E callback) {
synchronized (mCallbacks) {
Callback cb = mCallbacks.remove(callback.asBinder());
if (cb != null) {
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
return true;
}
return false;
}
}
mCallbacks.remove(callback.asBinder())以binder的引用来进行删除,使用这个List在unregisterListener(Listener listener)的时候,在客户端重写生成一个proxy也可以从List里删除了,因为所用的key为同一个binder,所以不存在有不能注销观察者的情况。
至此就把我心中的疑问所解释清楚了,不知道有没有解决大家的疑问,我这里可能有一些不正确的表述或者错误的地方,希望有知道的可以和我说一下。其实我们看源码,有的时候并不能立马可以给我们带来回报,可能我们或许不用知道内部是怎么实现的,我们也可以把这个程序写出来,但是看源码对我们以后做软件架构却有一些好处,到时你可以参考别人的架构,再结合自己的理解写出高质量的代码。
最后谢谢阅读!