Android实战技术:深入理解Android的RPC方式与AIDL

Understanding ADIL

AIDL是一个接口描述文件,用于实现Android平台上面的RPC,aapt在编译的时候会自动根据规则生成用于IPC的接口和对象,而作为使用者只需要:1.在服务端Service实现接口;2. 在客户端bindService,onServiceConnected时获取接口对象。这里的接口都是AIDL中描述的接口,其他的细节则在由AIDL生成的同名源码文件中。

揭开面纱

可以看一下gen文件夹下生成的与AIDL文件同名的源码文件,这个文件看似很复杂,通过这个文件来可以了解AIDL的本质,这里面有一个接口,里面在的方法就是AIDL文件中所定义的方法;还有一个Stub,这个就是我们要在Service端实现的基类;还有一个Proxy。它们之间的关系是这们的。

从使用者的角度来浏览这个源码文件:它的最外层是一个与AIDL同名的接口,这里是PrinterInterface,其内有一个接受String的方法print。Client端使用时是用PrinterInterface.Stub.asInterface,可以看到这个方法会返回一个实现了PrinterInterface接口的对象。另外就是Server端会让Service实现PrinterInterface.Stub,其实是实现PrinterInterface,因为Stub也继承自PrinterInterface。所以,貌似的时序是这样的:客户端获取了一个实现了PrinterInterface接口的对象,而服务端要实现此接口。
但是这样看起来还是有些乱,我们需要继续脱去它的衣服!(天热啊,得继续脱啊!)

脱去外套

因为由AIDL生成的文件无法编译,所以我们创建一个一模一样的文件来进行,以方便我们对其进行编辑和改动。我们分别在获取IBinder对象时,Stub的相关方法里和Proxy的相关方法里加上日志语句,以跟踪程序的行为:
通过跟踪调试可以得到以下结论:
当通讯的双方在同一个进程中时,onServiceConnected传回的对象是Service.onBind()所返回的对象;但如果是跨进程时,则其返回的是一个BinderProxy对象。所以,可以看到在AIDL生成的类中会有这样的判断:
	if (((iin != null) && (iin instanceof MyPrinterInterface))) {
		Log.e(TAG, "we have local interface, so we use it");
        	return ((MyPrinterInterface) iin);
	}

这实际上就是判断此通讯是在同一进程中,还是跨进程,因为同一进程传回的对象是Service.onBind()所返回的对象,而此对象必然实现了接口(要不然搞毛啊!)。所以,如果仅是在同一个进程之中,不会走Binder进程IPC,而是直接返回Service所提供的对象,直接调用其方法,因此也就不会有对象必须Parcelable的限制!
也就是说,当在同一个进程中时AIDL实际上变成了这样的:
也就是说它是直接返回了Service.onBind()的对象,这其实跟前面提到的第一种方式:直接实现Binder对象的方式是一样一样的,其他的代码全是多余的。因此,如前面建议的,如果仅是在同一个进程中,就直接使用Binder就好了,没有必要创建AIDL文件。
当在不同的进程中时,客户端Stub.asInterface会返回一个Stub.Proxy对象,调用其上的print方法。而服务端仅会执行Stub.onTransact()方法,然后就调到Service的print方法了。
当跨进程的时候,就要使用Binder对象的IPC相关的方法和机制。客户端需要实现Binder.transact()方法来执行远程的一个方法,这是给客户端来使用;而服务端则需要实现Binder.onTransact()来响应客户端所请求的方法。对于上层使用者来说,用transact()把函数的信息(参数,标识和开关)发送出去,剩下的就是Binder的工作了,内部还有大量的细节,但是最终会调用到服务端Binder的onTransact()方法,这里识别出函数的标识,然后调用具体的实现,再传回返回值,这样一个IPC的函数调用就完成了。
当跨进程时,仅以下代码是各自所必须的,去掉了无关代码:
Server service:

public class MyServerService extends Service {
	private static final String TAG = "MyServerService";
	private Handler mHandler = new Handler();
	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}
	
	private MyPrinterInterfaceStub mBinder = new MyPrinterInterfaceStub() {
		@Override
		public void print(String msg) throws RemoteException {
			MyServerService.this.print(msg);
		}
	};
	
    public void print(String msg) {
    	try {
    		Log.e(TAG, "Preparing printer...");
			Thread.sleep(1000);
			Log.e(TAG, "Connecting printer...");
			Thread.sleep(1000);
			Log.e(TAG, "Printing.... " + msg);
			Thread.sleep(1000);
			Log.e(TAG, "Done");
		} catch (InterruptedException e) {
		}
    	mHandler.post(new Runnable() {
    		@Override
    		public void run() {
    			Toast.makeText(MyServerService.this, "MyServerService Printing is done.", Toast.LENGTH_LONG).show();
    		}
    	});
    }
}

serer side interface definition:
public interface MyPrinterInterface extends android.os.IInterface {
	public void print(String msg) throws android.os.RemoteException;
}


abstract class MyPrinterInterfaceStub extends Binder implements MyPrinterInterface {
	private static final String DESCRIPTOR = "MyPrinterInterface";
	private static final String TAG = "MyPrinterInterfaceStub";


	public MyPrinterInterfaceStub() {
		attachInterface(this, DESCRIPTOR);
	}


	@Override
	public IBinder asBinder() {
		return this;
	}


	@Override
	public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
			throws android.os.RemoteException {
		Log.e(TAG, "onTransact, code is " + code);
		switch (code) {
		case INTERFACE_TRANSACTION: {
			Log.e(TAG, "onTransact, code is " + code + ", when this happens");
			reply.writeString(DESCRIPTOR);
			return true;
		}
		case TRANSACTION_print: {
			data.enforceInterface(DESCRIPTOR);
			String _arg0;
			_arg0 = data.readString();
			Log.e(TAG, "ontransact, arg is " + _arg0 + ", when this happened?");
			this.print(_arg0);
			reply.writeNoException();
			return true;
		}
		}
		return super.onTransact(code, data, reply, flags);
	}
	
	static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0);
}

Client activity:
public class AnotherMyClientActivity extends Activity {
    private static final String TAG = "MyClientActivity";
	MyPrinterInterface mService;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.printer_activity);
        setTitle("My interface another client Activity");
        ((Button) findViewById(R.id.play)).setText("Print via my interface");
    }


    @Override
    protected void onStart() {
        super.onStart();
        doBindService();
    }


	private void doBindService() {
		Intent intent = new Intent();
		intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.MyServerService");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
	}


    @Override
    protected void onStop() {
        super.onStop();
        doUnbindService();
    }


	private void doUnbindService() {
		if (mService != null) {
            unbindService(mConnection);
        }
	}
	
	public void onButtonClick(View v) {
		if (mService == null) {
			Log.e(TAG, "what the fucl service is not ready");
			return;
		}
		try {
			mService.print("In another application, create a client based on user defined IPC interfaces");
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
        	Log.e(TAG, "on service connected, service obj " + service);
            mService = MyPrinterInterface.Stub.asInterface(service);
        }


		@Override
        public void onServiceDisconnected(ComponentName arg0) {
        	mService = null;
        }
    };
}

client side interface definiition:
public interface MyPrinterInterface extends android.os.IInterface {
	public void print(String msg) throws android.os.RemoteException;
	
	public abstract class Stub extends Binder implements MyPrinterInterface {
		private static final String DESCRIPTOR = "MyPrinterInterface";
		private static final String TAG = "MyPrinterInterface.Stub";
		
		public Stub() {
			attachInterface(this, DESCRIPTOR);
		}
		
		public static MyPrinterInterface asInterface(IBinder obj) {
			if ((obj == null)) {
				return null;
			}


			Log.e(TAG, "we are talking to a remote one, we must use a proxy object to wrapper binder");
			return new Stub.Proxy(obj);
		}
		
		static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0);
		
		private static class Proxy implements MyPrinterInterface {
			private IBinder mRemote;
			
			Proxy(IBinder remote) {
				mRemote = remote;
			}
			
			@Override
			public IBinder asBinder() {
				return mRemote;
			}
			
			public String getInterfaceDescriptor() {
				return DESCRIPTOR;
			}
			
			@Override
			public void print(String msg) throws android.os.RemoteException {
				Parcel _data = Parcel.obtain();
				Parcel _reply = Parcel.obtain();
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					_data.writeString(msg);
					mRemote.transact(Stub.TRANSACTION_print, _data, _reply, 0);
					Log.e(TAG, "lalalala, let us passing the parameters and calling the message");
					_reply.readException();
				} finally {
					_reply.recycle();
					_data.recycle();
				}
			}
		}
	}
}

本质--脱去内衣

其实AIDL的作用就是对Binder的二个方法:Binder.transact()和Binder.onTransact()进行封装,以供Client端和Server端进行使用。因为实现transact()和onTransact()方法的方式基本上是相同的,所以就可以用模板来生成具体的代码。理论上讲只需要为Client端生成transact()相关代码,为服务端生成onTransact()代码即可,但因为工具无法准确的确定某一个应用到底是Client端还是Server端,所以它就生成所有的代码,放在一个文件中。这就是你看到的自动生成的文件。
还需要注意的一点是Client端的Proxy是组合Binder对象,调用其transact()方法;而服务端必须继承Binder对象,覆写onTransact()方法。为虾米呢?因为Client是主动发起IPC函数Call,所以它可以直接调用Binder的方法来进行IPC。而Server是被动的,是要接收进来的IPC call,但Service自己无法得知啥时候Call会来,因此必须实现回调(onTransact())给Binder,以让Binder在有IPC Call进来的时候告诉Service。

原理和内幕

AIDL的角色是实现Android平台上面的RPC(Remote Procedure Call)也即远程例程调用。RPC是IPC中的一种,但是它是以调用在本地或者另一个进程,甚至是另一个主机上的方法的机制。RPC的目的就是可以让程序不用担心方法具体是在哪个进程里面或者哪以机器上面,就像正常的本地方法那样去调用即可,RPC机制会处理所有的具体细节。RPC一般用IDL(Interface Definition Language)来描述,实现则要看具体的平台和语言。可以参考Wikipedia来看RPCIDL 的更多信息。
AIDL提供Android平台的RPC的支持:开发者仅需要要定义AIDL,做一些相关的适配工作,然后就可以使用这些方法了,不用具体关心接口描述的方法空究竟是在同一个进程中还是在其他的进程中。这些RPC实现的细节由Binder和系统来处理。
Binder RPC的流程:<Binder RPC sequence>

可以看到这个流程是一个标准的RPC流程。

不用AIDL来实现

知道了AIDL的本质后,就可以不用AIDL来实现IPC,虽然AIDL简单方便,但是它却非常不容易理解,而且代码有冗余(服务端并不需要为Client准备的对象,反之亦然)。



所以我们可以自已实现:
Server interface:
public interface ServerPrinterInterface {
	public void print(String msg) throws android.os.RemoteException;
}


abstract class MyPrinterInterfaceStub extends Binder implements ServerPrinterInterface, IInterface {
	private static final String DESCRIPTOR = "MyPrinterInterface";
	static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0);
	private static final String TAG = "MyPrinterInterfaceStub";


	public MyPrinterInterfaceStub() {
		attachInterface(this, DESCRIPTOR);
	}


	@Override
	public IBinder asBinder() {
		return this;
	}


	@Override
	public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
			throws android.os.RemoteException {
		Log.e(TAG, "onTransact, code is " + code);
		switch (code) {
		case INTERFACE_TRANSACTION: {
			Log.e(TAG, "onTransact, code is " + code + ", when this happens");
			reply.writeString(DESCRIPTOR);
			return true;
		}
		case TRANSACTION_print: {
			data.enforceInterface(DESCRIPTOR);
			String _arg0;
			_arg0 = data.readString();
			Log.e(TAG, "ontransact, arg is " + _arg0 + ", when this happened?");
			this.print(_arg0);
			reply.writeNoException();
			return true;
		}
		}
		return super.onTransact(code, data, reply, flags);
	}
}

service:
public class MyServerService extends Service {
	private static final String TAG = "MyServerService";
	private Handler mHandler = new Handler();
	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}
	
	private MyPrinterInterfaceStub mBinder = new MyPrinterInterfaceStub() {
		@Override
		public void print(String msg) throws RemoteException {
			MyServerService.this.print(msg);
		}
	};
	
    public void print(String msg) {
    	try {
    		Log.e(TAG, "Preparing printer...");
			Thread.sleep(1000);
			Log.e(TAG, "Connecting printer...");
			Thread.sleep(1000);
			Log.e(TAG, "Printing.... " + msg);
			Thread.sleep(1000);
			Log.e(TAG, "Done");
		} catch (InterruptedException e) {
		}
    	mHandler.post(new Runnable() {
    		@Override
    		public void run() {
    			Toast.makeText(MyServerService.this, "MyServerService Printing is done.", Toast.LENGTH_LONG).show();
    		}
    	});
    }
}

client interface:
public interface ClientPrinterInterface {
	public void print(String msg) throws android.os.RemoteException;
}


class MyPrinterInterfaceProxy implements ClientPrinterInterface {
	private static final String DESCRIPTOR = "MyPrinterInterface";
	static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0);
	private static final String TAG = "MyPrinterInterfaceProxy";
	private IBinder mRemote;


	MyPrinterInterfaceProxy(IBinder remote) {
		mRemote = remote;
	}


	@Override
	public void print(String msg) throws android.os.RemoteException {
		Parcel _data = Parcel.obtain();
		Parcel _reply = Parcel.obtain();
		try {
			_data.writeInterfaceToken(DESCRIPTOR);
			_data.writeString(msg);
			mRemote.transact(TRANSACTION_print, _data, _reply, 0);
			Log.e(TAG, "lalalala, let us passing the parameters and calling the message");
			_reply.readException();
		} finally {
			_reply.recycle();
			_data.recycle();
		}
	}
}

client activity:
public class AnotherMyClientActivity extends Activity {
    private static final String TAG = "MyClientActivity";
	ClientPrinterInterface mService;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.printer_activity);
        setTitle("My interface another client Activity");
        ((Button) findViewById(R.id.play)).setText("Print via my interface");
    }


    @Override
    protected void onStart() {
        super.onStart();
        doBindService();
    }


	private void doBindService() {
		Intent intent = new Intent();
		intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.MyServerService");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
	}


    @Override
    protected void onStop() {
        super.onStop();
        doUnbindService();
    }


	private void doUnbindService() {
		if (mService != null) {
            unbindService(mConnection);
        }
	}
	
	public void onButtonClick(View v) {
		if (mService == null) {
			Log.e(TAG, "what the fucl service is not ready");
			return;
		}
		try {
			mService.print("In another application, create a client based on user defined IPC interfaces");
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
        	Log.e(TAG, "on service connected, service obj " + service);
            mService = new MyPrinterInterfaceProxy(service);
        }


		@Override
        public void onServiceDisconnected(ComponentName arg0) {
        	mService = null;
        }
    };
}

从这里可以看到不使用AIDL有二个好处:
1. 自己实现还有一个好处就是可以随意设计包名。如果使用AIDL则Client端的AIDL文件所在package必须与Server端的AIDL的package完全一致,否则会找不到service,Client端会有异常。但如果自己实现接口,就没有此限制,可以把接口文件放在任何的package内。
为什么呢?因为AIDL生成的Stub和Proxy用的标识DESCRIPTOR加入了package的名字。而如果自己实现接口,可以任意的写这个DESCRIPTOR。
2. 接口的名字实际上无所谓,更进一步,其实方法的签名也可以不完全一致。因为这二个接口,一个是在Client端,另一个是在Server端。它们之间的联系是间接的通过Binder对象实现的。只要Binder对象的transact和onTransact二个方法能找到相应的接口方法即可。
关键的通讯标识和方法标识
从上面的例子可以看出客户Proxy的transact,和服务Stub的onTransact使用二个标识来识别对方:一个是DESCRIPTOR,这个是标识Binder的Token,也就是说是标识服务端和客户端;方法的标识就是TRANSACTION_print,是用来标识调用的是哪个方法。这个理解起来也不是很难,就好比打电话,先要通过通讯的标识电话号码找到相应的人,然后跟人说的时候要告诉他是哪件事(哪个方法)。
接下来可以二个方面来进行深入的研究:
1. bindService是如何获得Binder对象的(无论是本地时Service的实现,还是远程时的BinderProxy),或者说是如何查询到Binder对象。
这是ServiceConnection.onServiceConnected的调用栈:
2. Binder.transact()和Binder.onTransact()的细节,这也是具体Binder IPC机制的细节。
展开阅读全文
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值