一. 基本认识
在Android中,每个应用(Application)执行在它自己的进程中,无法直接调用到其他应用的资源,当一个应用被执行时,一些操作是被限制的,比如访问内存,访问传感器,等等。这样做可以最大化地保护系统,免得应用程序“为所欲为”。那我们有时需要在应用间交互,怎么办呢?于是,Android需要实现IPC协议。然而,这个协议还是有点复杂,主要因为需要实现数据管理系统(在进程或线程间传递数据)。为了暂时减缓这个“会呼吸的痛”,Android为我们实现了自己的IPC,也就是AIDL。
AIDL的语法很类似Java的接口(Interface),只需要定义方法的签名。AIDL支持的数据类型与Java接口支持的数据类型有些不同
- 所有基础类型(int, char, 等)
- String,List,Map,CharSequence等类
- 其他AIDL接口类型
- 所有Parcelable的类
下面来看一下如何使用AIDL
- 定义AIDL接口
在src目录下创建IMyService.aidl文件,这个只要是在eclipse中开发,你的adt插件会像资源文件一样把aidl文件编译成java代码生成在gen文件夹下,不用手动去编译:编译生成IMyService..java。
IMyService.aidl中代码
package com.chm.aidltest;
interface IMyService{
String getValue();
}
- 为远程服务(Service)实现对应Stub
首先我们在com.chm.aidltest这个包中新建一个类,取名为MyService.java。为了实现我们的服务,我们需要让这个类中的onBind方法返回一个IBinder类的对象。这个IBinder类的对象就代表了远程服务的实现。为了实现这个服务,我们要用到自动生成的子类IMyService..Stub。在其中,我们也必须实现我们之前在AIDL文件中定义的getValue()函数。下面是我们远程服务的代码:
public class MyService extends Service {
// public class MyServiceImpl extends IMyService.Stub {
// @Override
// public String getValue() throws RemoteException {
// return "Android开发";
// }
// }
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
// return new MyServiceImpl();
return mBinder;
}
private IMyService.Stub mBinder = new IMyService.Stub() {
@Override
public String getValue() throws RemoteException {
// TODO Auto-generated method stub
return "success";
}
};
}
- 绑定服务
一旦实现了服务中的onBind方法,我们就可以把客户程序(在这里是MainActivity.java)与服务连接起来了。为了建立这样的一个链接,我们需要实现ServiceConnection类。我们在MainActivity.java创建一个ServiceConnection类的成员变量serviceConnection ,并且重写了它的两个方法:onServiceConnected和onServiceDisconnected。下面给出内部类的代码。
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获得服务对象
myService = IMyService.Stub.asInterface(service);
btnInvokeAIDLService.setEnabled(true);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//释放资源
myService = null;
}
};
// 绑定AIDL服务,显示Intent启动
Intent mIntent = new Intent();
mIntent.setComponent(new ComponentName("com.mobile.aidl","com.mobile.aidl.IMyService"));
bindService(mIntent,serviceConnection, Context.BIND_AUTO_CREATE);
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
至此,整个功能流程都完成了
二. 深入理解AIDL
IMyService.java源码
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\workspace\\aidltest\\src\\com\\chm\\aidltest\\IMyService.aidl
*/
package com.chm.aidltest;
public interface IMyService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.chm.aidltest.IMyService
{
private static final java.lang.String DESCRIPTOR = "com.chm.aidltest.IMyService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.chm.aidltest.IMyService interface,
* generating a proxy if needed.
*/
public static com.chm.aidltest.IMyService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.chm.aidltest.IMyService))) {
return ((com.chm.aidltest.IMyService)iin);
}
return new com.chm.aidltest.IMyService.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_getValue:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getValue();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.chm.aidltest.IMyService
{
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.lang.String getValue() 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);
mRemote.transact(Stub.TRANSACTION_getValue, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getValue = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String getValue() throws android.os.RemoteException;
}
当通讯的双方在同一个进程中时,onServiceConnected传回的对象是Service.onBind()所返回的对象;但如果是跨进程时,则其返回的是一个BinderProxy对象。所以,可以看到在AIDL生成的类中会有这样的判断:
if (((iin!=null)&&(iin instanceof com.chm.aidltest.IMyService))) {
return ((com.chm.aidltest.IMyService)iin);
}
这实际上就是判断此通讯是在同一进程中,还是跨进程,因为同一进程传回的对象是Service.onBind()所返回的对象,而此对象必然实现了接口(要不然搞毛啊!)。所以,如果仅是在同一个进程之中,不会走Binder进程IPC,而是直接返回Service所提供的对象,直接调用其方法,因此也就不会有对象必须Parcelable的限制!
当在同一个进程中时AIDL实际上变成了这样的:
也就是说它是直接返回了Service.onBind()的对象,这其实和直接实现Binder对象的方式是一样一样的,其他的代码全是多余的。因此,建议, 如果仅是在同一个进程中,就直接使用Binder就好了,没有必要创建AIDL文件。
当在不同的进程中时,客户端Stub.asInterface会返回一个Stub.Proxy对象,调用其上的getValue方法。而服务端仅会执行Stub.onTransact()方法,然后就调到Service的getValue方法了。
当跨进程的时候,就要使用Binder对象的IPC相关的方法和机制。客户端需要实现Binder.transact()方法来执行远程的一个方法,这是给客户端来使用;而服务端则需要实现Binder.onTransact()来响应客户端所请求的方法。对于上层使用者来说,用transact()把函数的信息(参数,标识和开关)发送出去,剩下的就是Binder的工作了,内部还有大量的细节,但是最终会调用到服务端Binder的onTransact()方法,这里识别出函数的标识,然后调用具体的实现,再传回返回值,这样一个IPC的函数调用就完成了。
其实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。