1. 程式人生 > >跟面試官講Binder(二)之關於AIDL的認識

跟面試官講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。