转载

Android IPC - AIDL 学习总结

前面一篇文章对Binder的机制进行了总结,但作为一个应用层开发者,其实很少能使用到Binder相关的技术。实际上Android framework为我们封装了Binder,使我们只需要轻松编写Java代码就可以进程间通信了,这个神奇的封装就是AIDL。相信有不少开发者对这个名字都非常熟悉,但让他来讲一下AIDL具体机制和使用场景可能又说不清,那么我们今天就来聊一聊AIDL。
  1. Android IPC 为何设计AIDL
  2. AIDL的概述
  3. 如何使用AIDL
  4. AIDL的机制分析

Android IPC 为何设计AIDL

  1. AIDL是android为了方便开发者进行进程间通信,定义的一种语言。
  2. Android有一个跨进程间通信的工具Messenger,那为什么还设计AIDL呢?原因是AIDL可以处理多线程、多客户端并发访问的,而Messenger只能是单线程处理。
  3. Binder可以处理多线程、多客户端并发访问的进程间通信,那为什么还设计AIDL呢?实际上AIDL就是把Binder通讯的一些细节封装了起来,使得我们在JAVA层写IPC的代码时,不用考虑write、read、transact等操作,只需要把注意力专注在自身定义的接口上即可。
这么讲还是有点晕吧,这篇文章就带大家看看AIDL到底帮我做了哪些事情,以为我们如何更好的使用AIDL。

AIDL的概述

AIDL的基本概念

  • AIDL语言 : android interface definition language, Android接口描述语言,是接口描述语言中的一种。
  • 封装 : Android framework将Binder封装的几乎不可见了,应用开发者接触到的IPC大部分都是AIDL。
  • 面向接口 : AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。
  • CS模型 : AIDL和Binder一样,采用的是Client-Server的通信模型。
  • proxy模式 : 也和Binder一样,采用的是proxy设计模式,使用代理类在客户端和实现端传递数据。

AIDL的使用场景

官方文档特别提醒我们何时使用AIDL是必要的:只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。单线程的通信可以使用Messenger。
1. 定义AIDL接口: 在Server端用AIDL语言定义方法接口。和普通的接口内容没有什么特别,只是它的扩展名为.aidl,保存在src目录下。Android SDK tools就会在gen目录自动生成一个IBinder接口文件(java文件)。 2. 实现这些接口: Server端实现这个AIDL中的所有接口。 3. 调用这些接口: 客户端程序要copy这个aidl文件到自己的src目录,通过代码绑定service并在IPC时从IBinder中调用方法。

AIDL的注意事项

  • 对于Java编程语言的基本数据类型 (int, long, char, boolean等),String和CharSequence,集合接口类型List和Map,不需要import 语句。
  • 如果需要在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。
  • 对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。
  • AIDL只支持接口方法,不能公开static变量。

如何使用AIDL

创建AIDL文件

package com.liuzhiyong.calculate.aidl;

interface ICalculateAIDL {
    int add(int a,int b);
    int minus(int a,int b);
}
创建一个aidl文件“ICalculateAIDL.aidl”,这个接口里面定义了要对外提供的服务,我们这里定义了计算加法和减法的函数。

Server端实现

public class CalculateService extends Service {
  private static final String TAG="SERVER";
  public CalculateService() {
  }
  @Override
  public void onCreate() {
    super.onCreate();
    Log.e(TAG,"OnCreate");
  }
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    Log.e(TAG,"onStartCommand");
    return super.onStartCommand(intent, flags, startId);
  }
  @Override
  public IBinder onBind(Intent intent) {
    Log.e(TAG,"onBind");
    return mBinder;
  }
  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.e(TAG,"onDestroy");
  }
  @Override
  public boolean onUnbind(Intent intent) {
    Log.e(TAG,"onUnbind");
    return super.onUnbind(intent);
  }
  @Override
  public void onRebind(Intent intent) {
    Log.e(TAG,"onRebind");
    super.onRebind(intent);
  }
  private final ICalculateAIDL.Stub mBinder=new ICalculateAIDL.Stub(){
    @Override
    public int add(int a, int b) throws RemoteException {
      return a+b;
    }
    @Override
    public int minus(int a, int b) throws RemoteException {
      return a-b;
    }
  };
}
创建一个service,实现服务端方面的功能,顺手把生命周期中各方法都打印出来。重要的是生成一个ICalculateAIDL.Stub的mBinder对象(这个对象非常重要,我们后面再来讨论),并在onBind方法中返回它,这个mBinder完成了加法和减法函数的实现。 另外我们还需要在manifest文件中对该服务进行注册:
<service
  android:name=".CalculateService"
  android:enabled="true"
  android:exported="true" >
  <intent-filter>
    <action android:name="com.liuzhiyong.adil.calculate"/>
    <category android:name="android.intent.category.DEFAULT"/>
  </intent-filter>
</service>

Client端实现

@Override
public void onClick(View v) {
  int id=v.getId();
  switch (id){
    case R.id.btn_bind: //绑定service
      Intent intent=new Intent();
      intent.setAction("com.liuzhiyong.adil.calculate");
      bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
      break;
    case R.id.btn_unbind: //解绑service
      if (binded){
          unbindService(mConnection);
          binded=false;
      }
      break;
    case R.id.btn_add: //调用server端的加法
      if(mCalculateAIDL!=null){
        try {
          int res=mCalculateAIDL.add(3,3);
          txt_res.setText(res+"");
        } catch (RemoteException e) {
          e.printStackTrace();
        }
      }else{
        Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show();
      }
      break;
    case R.id.btn_minus://调用server端的减法
      if(mCalculateAIDL!=null){
        try {
          int res=mCalculateAIDL.minus(9,4);
          txt_res.setText(res+"");
        } catch (RemoteException e) {
          e.printStackTrace();
        }
      }else{
        Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show();
      }
      break;
  }
}
//连接Service的回调
private ServiceConnection mConnection=new ServiceConnection() {
  @Override
  public void onServiceConnected(ComponentName name, IBinder binder) {
    Log.e(TAG,"connect");
    binded=true;
    mCalculateAIDL=ICalculateAIDL.Stub.asInterface(binder);
  }
  @Override
  public void onServiceDisconnected(ComponentName name) {
    Log.e(TAG,"disconnect");
    mCalculateAIDL=null;
    binded=false;
  }
};
这段Client连接service方法应该很好理解,就不多废话了,下面就进入AIDL真正核心的机制分析。

AIDL的机制分析

上一章是个AIDL很简单的示例用法,并没有对其调用过程做分析,下面我们就开始从客户端的调用开始分析。

客户端绑定服务端

客户端调用bindservice方法来启动了service,连接服务端Service成功了会有回调:
public void onServiceConnected(ComponentName name, IBinder binder) {
    Log.e(TAG,"connect");
    binded=true;
    mCalculateAIDL= ICalculateAIDL.Stub.asInterface(binder);
}
我们从该onServiceConnected的参数中获取到一个Binder对象,我们通过此binder来与server进行通信。为了区分,我们称为clientBinder。

客户端得到ServerBinder

serverBinder = ICalculateAIDL.Stub.asInterface(binder); 我们先看一下Android tools帮我们生成的ICalculateAIDL.java有什么内容? Android IPC - AIDL 学习总结
package com.liuzhiyong.calculate.aidl;

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

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

        /**
         * Cast an IBinder object into an
         * com.liuzhiyong.calculate.aidl.ICalculateAIDL interface, generating a
         * proxy if needed.
         */
        public static com.liuzhiyong.calculate.aidl.ICalculateAIDL asInterface(
                android.os.IBinder 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 {
                ......
        }

        private static class Proxy implements
                com.liuzhiyong.calculate.aidl.ICalculateAIDL {
            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 int add(int a, int b) throws android.os.RemoteException {
                ......
            }

            @Override
            public int minus(int a, int b) throws android.os.RemoteException {
                ......
            }
        }
    }
    public int add(int a, int b) throws android.os.RemoteException;

    public int minus(int a, int b) throws android.os.RemoteException;
}
现在我们来具体研究下代码,其实这里面的重点很简单,一个是Stub类,一个是Proxy类。
  1. 先看Stub类,Stub翻译成中文是存根的意思 Stub对象是在服务端进程,是ICalculateAIDL的内部类,它继承自Binder并实现了这个生成的java接口ICalculateAIDL。讲了这么多,Stub究竟怎么理解呢。 实际上,stub是为了帮助客户端和服务端通信的一个中介,Service的实现类需要去继承这个stub服务桩类,客户端bind服务端的service返回了一个clientBinder对象,通过调用ICalculateAIDL.Stub.asInterface(clientBinder);得到服务端的Binder。 asInterface这个方法能得到Serverbinder对象了,那我们来看下asInterfce干了什么:
    public static com.liuzhiyong.calculate.aidl.ICalculateAIDL asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        //根据包名获取本地实现的一个接口的实例,如果是本地service则可以获取到
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.liuzhiyong.calculate.aidl.ICalculateAIDL))) {
            //如果得到的实例是ICalculateAIDL的对象,则返回
            return ((com.liuzhiyong.calculate.aidl.ICalculateAIDL) iin);
        }
        //如果无法得到本地实现的对象则会返回一个代理对象
        return new com.liuzhiyong.calculate.aidl.ICalculateAIDL.Stub.Proxy(obj);
    
        //简单来看,就做了两件事,首先在系统中查找注册的的service.
        //如果没有找到,那么一定是别的apk(别的进程)实现的service,于是返回一个此service的静态代理类对象供Client调用。
        //也就是什么意思呢,其实就是去查询是不是在同一个应用里去调用它,如果是同一个应用,直接本地调用就可以了。
        //如果不是本地接口,这时候会返回一个Proxy对象。
    }
  2. 再看Proxy类,客户端通过proxy来调用服务端的接口方法 Stub的一个内部类,也同样实现了ICalculateAIDL接口 接下来,我们重点来研究下Proxy类,这个东东又在搞什么?
    private static class Proxy implements com.liuzhiyong.calculate.aidl.ICalculateAIDL {
          private android.os.IBinder mRemote;
          Proxy(android.os.IBinder remote) {
            mRemote = remote;
          }
          ......
          @Override
          public int add(int a, int b) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            int _result;
            try {
              _data.writeInterfaceToken(DESCRIPTOR);
              //将参数打包
              _data.writeInt(a);
              _data.writeInt(b);
              //调用binderDriver的提供的方法将参数发给服务端
              mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
              _reply.readException();
              //读取到返回结果
              _result = _reply.readInt();
            } finally {
              _reply.recycle();
              _data.recycle();
            }
            return _result;
          }
          @Override
          public int minus(int a, int b) throws android.os.RemoteException {
            ......//同上
          }
        }
    }
首先proxy的构造函数把我们将clientBinder传进来了,同时它也实现了ICalculateAIDL接口,我们以add方法为例,里面将参数打包发送给Server端。在Server端收到请求后,会调用到Stub类中的onTransact方法,具体onTransact()方法如下:
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_add: {
          data.enforceInterface(DESCRIPTOR);
          int _arg0;
          _arg0 = data.readInt();
          int _arg1;
          _arg1 = data.readInt();
          //调用我们实现好的方法
          int _result = this.add(_arg0, _arg1); 
          reply.writeNoException();
          //把结果返回
          reply.writeInt(_result); 
          return true;
        }
        case TRANSACTION_minus: {
          data.enforceInterface(DESCRIPTOR);
          int _arg0;
          _arg0 = data.readInt();
          int _arg1;
          _arg1 = data.readInt();
          int _result = this.minus(_arg0, _arg1);
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}
在onTransact中,真正调用add()或者minus()方法,得到返回结果,然后结果打包返回给Proxy处理,最后返回给客户端。

总结,以add()为例

1. 客户端绑定Service: 客户端绑定服务端的service,绑定成功后回调onServiceConnected,在参数里得到ClientBinder。
2. 客户端得到ServerBinder: 客户端通过mCalculateAIDL = ICalculateAIDL.Stub.asInterface(binder);这个方法得到真正可使用的ServerBinder对象mCalculateAIDL。asInterface会判断此binder是不是本地binder,如果是本地的(在一个进程中),直接返回这个binder对象。 如果是远端的(不在一个进程中),返回一个proxy对象,这个proxy也实现了ICalculateAIDL接口。
3. 客户端调用add(): Client端通过ServerBinder调用add方法发送给服务端:mCalculateAIDL.add(3,3);
4. 服务端处理add(): 如果是本地binder对象,直接调用add()方法。如果是远端binder对象,proxy代理调用add方法,把相关参数打包发给Server端,Server收到后,在Stub中调用onTransact()方法,此时会调用真正的add方法,然后把结果打包发回给Client端。
5. 客户端得到result: Client收到计算结果6,over。
AIDL 交互过程client<–>proxy<–>stub<–>service stub和proxy是为了方便client/service交互而生成出来的代码,这样client/service的代码就会比较干净,不会嵌入很多很难懂的与业务无关的代码。这也是AIDL的目标,让开发者只关心自己设计的业务接口,不关心底层binder传输的细节。

引用资料

正文到此结束
Loading...