关于对Android使用AIDL进行IPC通讯的一点见解

大家应该都使用过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,所以不存在有不能注销观察者的情况。

至此就把我心中的疑问所解释清楚了,不知道有没有解决大家的疑问,我这里可能有一些不正确的表述或者错误的地方,希望有知道的可以和我说一下。其实我们看源码,有的时候并不能立马可以给我们带来回报,可能我们或许不用知道内部是怎么实现的,我们也可以把这个程序写出来,但是看源码对我们以后做软件架构却有一些好处,到时你可以参考别人的架构,再结合自己的理解写出高质量的代码。
最后谢谢阅读!



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值