Android IPC - AIDL 学习总结

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

  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等操作,只需要把注意力专注在自身定义的接口上即可

其实Messenger、AIDL、Binder这三个东西的关系是这样的: AIDL是对Binder的封装,Messenger是对AIDL的封装,就是为了更好的方便开发者在不同场景下使用进程间通信
这么讲还是有点晕吧,这篇文章就带大家看看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中处理多线程。这种场景才需要用到AIDL

1. 定义AIDL接口
在Server端用AIDL语言定义接口。和普通的接口内容没有什么特别,只是它的扩展名为.aidl,保存在src目录下。Android SDK tools就会在gen目录自动生成一个IBinder接口文件(java文件)。
2. 实现这些接口:
Server端实现这个AIDL中的所有接口。
3. 调用这些接口:
客户端程序要copy这个aidl文件到自己的src目录,通过代码绑定service并在IPC时从proxy中调用方法。


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 IBinder onBind(Intent intent) {
    Log.e(TAG,"onBind");
    return mBinder;
  }
  ......
  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完成了加法和减法函数的实现。

Client端实现
  1. 连接Server:

    //连接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;
      }
    };
    
  2. 调用Server端的方法:

    @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;
      }
    }
    

这段Client连接service方法应该很好理解,就不废话了。
看到了么,使用AIDL来IPC,客户端和服务端就这么点代码。
下面我们就进入AIDL真正核心的机制分析。


AIDL的机制分析

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

客户端绑定服务端得到proxy

客户端调用bindservice方法来启动了service,连接服务端Service成功了会有回调:

public void onServiceConnected(ComponentName name, IBinder binder) {
    Log.e(TAG,"connect");
    binded=true;
    proxy = ICalculateAIDL.Stub.asInterface(binder);
}

在onServiceConnected的参数中获取到一个Binder对象,通过binder得到proxy,然后就可以通过proxy调用服务端的接口。
接下来我们会分析proxy是怎么获得的?


客户端得到proxy

proxy = ICalculateAIDL.Stub.asInterface(binder);
我们先看一下Android tools帮我们生成的ICalculateAIDL.java有什么内容?

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";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static com.liuzhiyong.calculate.aidl.ICalculateAIDL asInterface(
                android.os.IBinder obj) {
                ......
        }

        @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 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这个方法能得到proxy对象了,那我们来看下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);
    
        //简单来看,就做了两件事
        //1. 首先在Client进程中这个名字的service,找到了,就返回Stub,本地调用。
        //2. 如果没有找到,那么一定别的进程实现的service,于是返回一个service的静态代理类对象(proxy)供Client调用。
        //其实就是去查询是不是在同一个进程里去调用的,如果是同一个进程,直接本地调用。如果不是一个进程,会返回一个Proxy对象供远程RPC。
    }
    
  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也实现了ICalculateAIDL接口。我们以add方法为例,在add方法中将请求函数命令(Stub.TRANSACTION_add)、参数(data)和返回结果(reply)都打包发送给Binder驱动。Binder驱动会将这个请求函数、参数和返回结果发送给Server端,Server端在Stub类中的onTransact方法中收到这个请求包裹,然后真正执行相关的调用,把执行结果打包回传给Binder驱动,最后Binder驱动把结果回传给Client端,Client端把结果解析出来,整个交互就结束了。
具体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,在参数里有一个ServerBinder,通过这个binder得到一个proxy对象,供RPC使用。

2. 客户端得到Proxy
客户端通过proxy = ICalculateAIDL.Stub.asInterface(binder);这个方法得到proxy对象。asInterface会判断此ServerBinder是不是本地binder,如果是本地的(在一个进程中),直接返回Stub对象,本地调用方法即可。 如果是远端的(不在一个进程中),返回一个proxy对象,这个proxy也实现了AIDL的ICalculateAIDL接口。

3. 客户端调用add()
Client端通过proxy调用AIDL的add接口:proxy.add(3,3);

4. 服务端处理add请求,把计算结果回传给客户端
如果是本地Stub对象,直接调用add()方法。如果是proxy对象,proxy对象会调用他的add方法(注意这里,由于proxy也实现了AIDL的ICalculateAIDL接口,所以他也有add和minus方法,这就是proxy设计模式),在add方法中把相关命令、参数和空结果打包发给Binder驱动,Binder驱动将包裹发送给对应的Server端,Server端在Stub类的onTransact()方法中收到,执行真正的add方法调用,得到计算结果,然后把结果打包发回给Binder驱动,Binder驱动再回给Client端,Client解析包裹,得到计算结果,over。

AIDL 交互过程client<–>proxy<–>stub<–>service
从上述交互可以看到,stub和proxy是为了方便client/service交互而生成出来的类,这样client/service的代码就会比较干净,不会嵌入很多很难懂的与业务无关的代码。这也是AIDL的目标,让开发者只关心自己设计的业务接口,不关心底层binder传输的细节。

引用资料

  1. 从AIDL开始谈Android进程间Binder通信机制

  2. Android service 中的stub类是什么意思?

  3. 彻底明白Android中AIDL及其使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值