跟面试官讲Binder(二)之关于AIDL的认识

面试官开口说:“听你刚才所说,在Android系统中,都是利用Binder来进行进程间通信的,那我怎么听说,还有利用AIDL来实现进程间通信的呢?”。

其实,AIDL只是一种描述性语言,其全称是Android Interface Definition Language,即接口定义语言,利用ADT,我们可将自定义的AIDL文件转化成Java代码,而这些代码就能够来进行进程间通信(IPC)。为什么这些代码就能够进行IPC呢?那是因为,这些代码就是定义了Binder机制中作为服务端的Binder对象和客户端中用的Proxy对象。

在前面的文章中,我们讲过,作为服务端的服务,会在本线程中创建一个Binder对象,并将其引用传递给Binder驱动,而客户端通过Binder驱动获得对应的Binder对象的引用时,其获得的其实是一个Proxy对象,然后其通过这个Proxy对象去跟驱动,再由驱动去跟服务端的Binder对象进行通信。

而通过AIDL生成的代码,我们就可以从代码的层面来帮助我们更好地理解关于Binder机制的作用了。

看来,面试变成了上机操作。。。

我们还是先写一份AIDL文件,定义一个接口和对应的方法。

首先,我为什么是定义接口呢?仔细想想,其实在不同的进程中进行通信,无非就是想使用彼此的服务,而服务的一个非常好的表现形式就是对接口编程,也就是说,我们只需要知道服务提供的名称是什么,需要什么参数,而具体服务端怎么实现,我们并不关心。当服务端改变了实现,它也只需要保持接口的一致性,就不会影响客户端的使用。

package com.lms.aidl;

import java.util.List;
import com.lms.aidl.Bean;

interface ITestService {

	List<Bean> getBean();
	
	void addBean(in Bean bean);
}

看起来很像Java中的接口嘛。

利用ADT插件,在Eclipse中会生成如下的java代码

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: ...\\AidlServer\\src\\com\\lms\\aidl\\ITestService.aidl
 */
package com.lms.aidl;

public interface ITestService extends android.os.IInterface {
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements
			com.lms.aidl.ITestService {
		private static final java.lang.String DESCRIPTOR = "com.lms.aidl.ITestService";

		/** Construct the stub at attach it to the interface. */
		public Stub() {
			this.attachInterface(this, DESCRIPTOR);
		}

		/**
		 * Cast an IBinder object into an com.lms.aidl.ITestService interface,
		 * generating a proxy if needed.
		 */
		public static com.lms.aidl.ITestService asInterface(
				android.os.IBinder obj) {
			if ((obj == null)) {
				return null;
			}
			android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
			if (((iin != null) && (iin instanceof com.lms.aidl.ITestService))) {
				return ((com.lms.aidl.ITestService) iin);
			}
			return new com.lms.aidl.ITestService.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 {
			switch (code) {
			case INTERFACE_TRANSACTION: {
				reply.writeString(DESCRIPTOR);
				return true;
			}
			case TRANSACTION_getBean: {
				data.enforceInterface(DESCRIPTOR);
				java.util.List<com.lms.aidl.Bean> _result = this.getBean();
				reply.writeNoException();
				reply.writeTypedList(_result);
				return true;
			}
			case TRANSACTION_addBean: {
				data.enforceInterface(DESCRIPTOR);
				com.lms.aidl.Bean _arg0;
				if ((0 != data.readInt())) {
					_arg0 = com.lms.aidl.Bean.CREATOR.createFromParcel(data);
				} else {
					_arg0 = null;
				}
				this.addBean(_arg0);
				reply.writeNoException();
				return true;
			}
			}
			return super.onTransact(code, data, reply, flags);
		}

		private static class Proxy implements com.lms.aidl.ITestService {
			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;
			}

			@Override
			public java.util.List<com.lms.aidl.Bean> getBean()
					throws android.os.RemoteException {
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				java.util.List<com.lms.aidl.Bean> _result;
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					mRemote.transact(Stub.TRANSACTION_getBean, _data, _reply, 0);
					_reply.readException();
					_result = _reply
							.createTypedArrayList(com.lms.aidl.Bean.CREATOR);
				} finally {
					_reply.recycle();
					_data.recycle();
				}
				return _result;
			}

			@Override
			public void addBean(com.lms.aidl.Bean bean)
					throws android.os.RemoteException {
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					if ((bean != null)) {
						_data.writeInt(1);
						bean.writeToParcel(_data, 0);
					} else {
						_data.writeInt(0);
					}
					mRemote.transact(Stub.TRANSACTION_addBean, _data, _reply, 0);
					_reply.readException();
				} finally {
					_reply.recycle();
					_data.recycle();
				}
			}
		}

		static final int TRANSACTION_getBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
		static final int TRANSACTION_addBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
	}

	public java.util.List<com.lms.aidl.Bean> getBean()
			throws android.os.RemoteException;

	public void addBean(com.lms.aidl.Bean bean)
			throws android.os.RemoteException;
}

我们来仔细看这份代码,可以看到,就是定义了三个类:

1)接口ITestService和其对应的方法

2)Stub类

3)Proxy类

其中,Stub类是一个抽象类,继承了Binder类,并且实现了ITestService。其实这就是在我们定义服务端服务时需要去实现的Binder类,也就是说,当我们创建一个服务,并且希望这个服务能够被跨进程使用的话,我们就可以在我们的服务中去实现这样一个Stub类,在其定义的方法中去实现对应的逻辑,这个我在下一篇文章中会给出对应的例子。

而在Stub类中呢,又定义了一个Proxy类,同样也实现了ITestService接口,所以对于客户端进程来说,其与Proxy通信,感觉就会像跟服务器端的Binder类通信一样,因为两边暴露出来的方法都是一样的,这也是设计模式中代理模式的一个非常典型的应用。

而实际上Proxy类则是通过一个IBinder类型的mRemote对象来跟驱动进行交互,并将对应的数据信息通过驱动与服务端的进程进行交互,而Android的Binder类,其实也是实现了IBinder接口,如下:

public class Binder implements IBinder {
    /*
     * Set this flag to true to detect anonymous, local or member classes
     * that extend this Binder class and that are not static. These kind
     * of classes can potentially create leaks.
     */
    private static final boolean FIND_POTENTIAL_LEAKS = false;
    private static final String TAG = "Binder";

    private int mObject;

所以在这里,Proxy类中,又可以将mRemote对象当成服务端的Binder对象来对待,跟mRemote对象通信就相当于跟服务端的Binder对象通信了。

说了这么多次服务端的是用Stub类,也即Binder对象,客户端的是使用Proxy类,虽然Proxy类中也是利用mRemote这个Binder接口,那么,在具体的进程中,比如就是A进程,它是怎么去判断拿哪个对象呢?到底是拿Stub类呢,还是拿Proxy类呢?

这一点,我们也可以从上面这份代码中看出来哦!面试官好像感觉有点意思的样子呢!!!!

我们在Stub类中可以看到如下方法:

		/**
		 * Cast an IBinder object into an com.lms.aidl.ITestService interface,
		 * generating a proxy if needed.
		 */
		public static com.lms.aidl.ITestService asInterface(
				android.os.IBinder obj) {
			if ((obj == null)) {
				return null;
			}
			android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
			if (((iin != null) && (iin instanceof com.lms.aidl.ITestService))) {
				return ((com.lms.aidl.ITestService) iin);
			}
			return new com.lms.aidl.ITestService.Stub.Proxy(obj);
		}

这是将一个IBinder接口的对象转变成我们定义的接口对象,在这里,我们可以发现,当某进程去将某IBinder接口对象转化成接口时,其会先去利用IBinder对象的queryLocalInterface方法去获取有没有本地的接口对象,也即Stub对象,如果没有的话,它就会创建一个Proxy对象?为什么会这样呢?因为找不到的话,就说明那个Binder对象并不是在本进程内,那就是要进行进程间通信,那你是异进程,当然要创建一个Proxy对象,我就是这么理解的,虽然说起来很绕,很晕,但我就觉得这样想应该是对的。

或者,我们再看进去Binder类中的queryLocalInterface方法,

    public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

这个descriptor是我们创建这份代码里自动生成的一个常量,其实就是指定了当前服务的描述符。如果相同,就会返回mOwner,而mOwner就是实现这个接口的服务的一个实例,其是在attachInterface方法中定义的,如下:

    public void attachInterface(IInterface owner, String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }

而我们从Stub的构造函数中可以看到,owner就是我们实现的某个Stub对象,对吧。

		/** Construct the stub at attach it to the interface. */
		public Stub() {
			this.attachInterface(this, DESCRIPTOR);
		}

不仅如此,Stub类中,还给定义的接口方法定义了标识符,如下:

		static final int TRANSACTION_getBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
		static final int TRANSACTION_addBean = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

利用这些标识符,再通过onTransact方法去获取驱动中的数据,就能够知道客户端是想要使用服务端哪个方法了。

啊!差点忘了,onTransact方法是干什么的?

其实呀,从上面AIDL生成的这份代码中,我们就可以看到,onTransact方法其实主要就是在用户空间和内核空间中进行数据的交换,也就是实现进程间数据的交互,从而来通知彼此应该要干什么事。

传递的数据都在Parcel参数data和reply中,关于这些,我觉得无非就是数据交互所定的协议和规范,读取顺序之类的东西,好像真要讲,我得去多多补充知识才能讲得清楚。

太晚了,面试官都睡着了,这打呼的声音。。。。

稍微总结一下,AIDL文件,其实是一份辅助的文件,因为有了ADT的存在,其才能发挥作用,因为ADT能够根据我们定义的AIDL文件,生成对应的Stub类和Proxy类等Binder机制相关的代码。

所以,对于一些大牛来说,完全可以不需要AIDL文件,直接就可以写出ADT生成的这些代码,并实现进程间的通信,所以AIDL,只是简化IPC开发的一个小工具而已,其实跟IPC本身并没有什么关系。

当然也就不能说利用AIDL来实现IPC,最多只能说,利用AIDL和ADT来实现Binder机制,从而实现IPC。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值