首页 > 技术文章 > AIDL跨进程通信

not2 2021-04-21 15:53 原文

一、IPC机制

 IPC(Inter-Process Communication),即进程间通信。此技术并非Android独创,其他系统也存在IPC机制。比如,Linux系统有:socket,pipe,signal,trace等。Android虽然继承了Linux内核,但是几乎没有使用这些,取而代之的是大量使用Binder机制,主要原因是其通信效率和安全性更高。

序列化,当我们通过Intent或Binder传输数据的时候需要对数据进行序列化。可以通过Serialiazable或Parcelable接口完成序列化:

  • Serialiazable Java提供的接口,使用简单,内存开销大,适用于数据持久化或网络传输。
  • Parcelable Android提供的接口,使用复杂,内存开销小,适用于内存间数据传输。

Android主要的IPC方式:

  • Bundle
  • 文件共享
  • Messenger
  • AIDL
  • ContentProvider

 Bundle本身实现了Parcelable接口,只需要先将数据放入Bundle,再通过Intent就可以方便地在进程间传输。然而Intent机制传输效率低,大多用于应用层的功能整合。

 Binder带来更高效率的进程间通信,主要用于系统层的功能整合。Messenger、AIDL、ContentProvider底层都是基于Binder机制。


 

二、Binder原理

Binder是Android提供的一种效率更高、更安全的基于C/S架构的IPC通信机制,其本质也是调用系统底层的内存共享实现。

从进程角度来看Binder进程间通信原理:

binder

在Android系统中

  • 用户空间彼此不能共享
  • 内核空间可以共享
  • 用户空间进程通过open/ioctl等方式与内核空间通信
  • Client与Server通信的实现,是由Binder驱动在内核空间完成

 在android中,有很多Service都是通过binder来通信的。这里要引入另一个重要的角色:ServiceManager。ServiceManager负责管理Server所提供的服务,同时响应Client的请求并为之分配相应的服务。

 可以把ServiceManager(以下简称SM)比作通讯站,Client和Server是电话用户,Server首先要向通讯站注册号码,当Client拨打号码时,通讯站首先检索是否有该号码,如果有则转接给Client,否则不响应。

 需要注意的是,Client一般认为是数据发送方,Server是数据接收方,两者并非固定不变的。如果Server在收到数据后主动向Client方发送数据,则两者身份互换。

Binder通信的完整流程如下:

Binder流程

  • Server向ServiceManager注册服务
  • Client向ServiceManager申请服务
  • SM作为守护进程,处理Client端请求,并管理所有Server端服务
  • BinderDriver位于Kernel层,是一切运作的基础

(1)Server向SM注册服务

Server在自己的进程向Binder驱动申请创建某服务Service的Binder实体。

Binder驱动收到申请后创建该Service的Binder实体和引用,并将该Service的名字和引用发送给SM。

SM收到数据后,提取该Service的名字和引用,存入查询表。

这时如果有Client向SM申请该Service服务,SM从查询表查找该Service的引用,并发送给Client。

注意:SM保存的只是Service的引用,Service的Binder实体位于Binder驱动中。我们将实体称为本地对象,引用称为远程对象。

(2)Client从SM获取Service的远程对象

  上面说了,Client进程向SM发送消息获取Service引用,进而调用Server进程的Service服务,从而实现了Client与Server进程间通信。

那么,问题来了,Client和SM本身就是两个不同的进程。当Client向SM申请服务的时候,SM实际上扮演了Server进程的角色。也就是说,我们要利用Client和SM进程间通信来完成Client和Server进程间通信。这岂不是很荒谬么,Client和Server之间可以通过SM来协调,那Client和SM之间又怎么协调呢?问题似乎变成了先有鸡还是先有蛋的悖论。

或许凡人会纠结先有鸡还是先有蛋,但上帝不会。因为上帝可以创造,他随便创建一个鸡或者蛋,问题不就解决了。Google的程序员就是Android的上帝,果然,他们创造了一只“鸡”:当Binder驱动启动后,SM会通过BINDER_SET_CONTEXT_MGR命令将自己注册为SM,此时,Binder驱动会创建一个SM的Binder实体。而且,这个Binder实体不需要生成引用,他们和Client和Server达成了协议:handle值为0的引用就是指向SM。所以,Client和Server不需要再借助第三者就能自己生成SM的引用。也就是说,只要Binder驱动启动后,Client就可以跨进程向SM申请服务,Server也可以跨进程向SM注册服务。

详细的进程间通信流程如下:

  1. Binder驱动启动,并生成SM的Binder实例
  2. Server端通过handle为0的引用向SM注册自己的Service名字和引用
  3. Client端通过handle为0的引用向SM申请访问某个Service
  4. SM通过名字查询对应的Service引用,并返回给Client端,如果有更多的Client访问该Service,则每个Client都会获得这个Service的引用
  5. Client端通过Service引用向Server端发送数据,此时Server端也获得了Client引用
  6. Server端通过Client引用向Client端返回数据

至此,一个完整的C/S通信架构建成了!


 

三、AIDL概述

 上面讲的Binder进程间通信原理已经普遍存在于Android系统层,比如SystemServer中的PowerManagerServiceActivityManagerService DisplayManagerService、PackageManagerService等都是通过Binder机制向外提供跨进程服务的。

以上都是系统级服务,如何在应用层通过Binder机制实现自己的IPC通信呢?

答案是AIDL,AIDL是Binder机制向外提供的接口,是Java层Binder实现的便利工具。

AIDL全称为Android接口定义语言,是一种IDL语言,它可以生成一段代码,通过预先定义的接口达到两个进程内部通信(IPC)的目的。比如一个进程中的Activity想访问另一个进程中Service,就可以通过AIDL生成代码来实现两者之间的数据交互。

从整体上来看看AIDL在Android系统进程间通讯的位置:

IPC

Android系统IPC架构中:

  • 最底层依然调用Kernel层的内存共享
  • Binder作为Android提供的最主要IPC机制
  • AIDL是Binder的Java层实现
  • Messager对AIDL进行封装,且是线程安全的
  • Intent、ContentProvider、BroadcastReceiver是对Binder更高层级的封装

 

四、AIDL实例

下面通过一个简单的AIDL实例,来验证Android的Binder机制。

创建两个app:Client和Server。Client端界面可以输入两个数值a和b,点击“计算”按钮,调用Server端的SumService服务计算a+b,并将结果返回给Client端显示。

(1)创建Server项目

 

 其中ISumServiceInterface.aidl是声明的aidl接口,编译后会在build目录下自动生成对应的ISumServiceInterface.java类。

// ISumServiceInterface.aidl
package com.example.server;

// Declare any non-default types here with import statements
interface ISumServiceInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    //此处声明对外提供的服务方法
    int sum(int a, int b);
}

SumService是本地服务类,sum方法的真正执行者。

public class SumService extends Service {
    @Nullable
    public static final String TAG = "aidl";
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "server端:onBind");
        return iBinder;
    }

    //ISumServiceInterface.Stub是AIDL自动生成的静态内部类,iBinder将返回给client端
    private IBinder iBinder = new ISumServiceInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public int sum(int a, int b) throws RemoteException {
            Log.e(TAG,"server端:收到Client端请求:a=" + a + ",b=" + b );
            return a + b;
        }
    };
}

 

AndroidManifest中配置SumService

       <service android:name=".SumService"
            android:process=":remote"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.server.sum_service"/> <!--client端绑定服务时需使用相同的action-->
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

(2)创建Client项目

ISumServiceInterface.aidl和server端是一样的(注意包名也必须一样),编译后同样自动生成ISumServiceInterface.java类。

MainActivity允许client端用户输入两个数值,然后远程调用server端的sum方法,最后将server端返回的结果显示在client端。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "aidl";
    private EditText a = null;
    private EditText b = null;
    private TextView display;
    private Button sum;
    private Button bind;
    private ISumServiceInterface sumServiceInterface;
    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "client端:服务已绑定!");
            //这里调用Stub.asInterface实际上获得了server端ISumServiceInterface.Stub的远程对象
            sumServiceInterface = ISumServiceInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "client端:服务已断开!");
            sumServiceInterface = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        a = findViewById(R.id.a);
        b = findViewById(R.id.b);
        sum = findViewById(R.id.sum);
        bind = findViewById(R.id.bind);
        display = findViewById(R.id.display);
        sum.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    int n1 = Integer.parseInt(a.getText().toString().trim());
                    int n2 = Integer.parseInt(b.getText().toString().trim());

                    //sumServiceInterface是server端ISumServiceInterface.Stub的远程对象
                    //通过它就可以调用server端的sum方法
                    int result = sumServiceInterface.sum(n1, n2);
                    Log.i(TAG, "client端:收到server端返回结果:" + result);
                    display.setText("结果:" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService();
            }
        });
    }

    private void bindService() {
        Log.i(TAG, "client端:开始绑定SumService...");
        Intent intent = new Intent();
        //这里的Action和Server端配置的必须保持一致
        intent.setAction("com.example.server.sum_service");
        //要求显示调用,指定包名
        intent.setComponent(new ComponentName("com.example.server", "com.example.server.SumService"));
        //绑定并自动创建服务
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }
}

 

(3)安装并运行

安装client和server,运行client输入55和45,点击BIND,点击SUM,结果显示:

client

 

Log打印如下:

I aidl    : client端:开始绑定SumService...
I aidl    : server端:onBind
I aidl    : client端:服务已绑定!
E aidl    : server端:收到Client端请求:a=55,b=45
I aidl    : client端:开始绑定SumService...
I aidl    : server端:onBind
I aidl    : client端:服务已绑定!
E aidl    : server端:收到Client端请求:a=55,b=45
I aidl    : client端:收到server端返回结果:100

可以看出,成功从client端调用server端sum方法并返回结果。


 

 

五、AIDL底层逻辑

从上面的实例已经知道如何使用AIDL,下面分析一下AIDL真正的底层逻辑,看它是如何使用Binder机制实现IPC通信的。

当我们声明ISumServiceInterface.aidl并编译后,自动生成了ISumServiceInterface.java类。这个类才是真正实现Java层与Binder驱动交互的。

下面详细分析ISumServiceInterface.java代码(删减部分冗余代码):

public interface ISumServiceInterface extends android.os.IInterface {
  /**
   * Local-side IPC implementation stub class.
   */
  public static abstract class Stub extends android.os.Binder implements com.example.server.ISumServiceInterface {

    //DESCRIPTOR是Binder服务的唯一标志,一般用完全类名表示
    private static final java.lang.String DESCRIPTOR = "com.example.server.ISumServiceInterface";

    /**
     * Construct the stub at attach it to the interface.
     * Stub构造函数,在SumService的onBind()时实例化并作为IBinder返回给client
     */
    public Stub() {
      //表示将Stub实例绑定为ISumServiceInterface的本地实现,此后,queryLocalInterface(DESCRIPTOR)将返回此本地实现,而不是null
      this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an com.example.server.ISumServiceInterface interface,
     * generating a proxy if needed.
     * 这个方法在client端调用,用于将SumService返回的IBinder转化成ISumServiceInterface实例,分两种情况:
     * 1.client有ISumServiceInterface的本地对象,那就直接返回,也就不需要IPC了
     * 2.本例中Stub构造函数是在Server端SumService的onBind()时调用的,client端没有本地对象,
     * 所以需要生成并返回一个Proxy,即远程对象,然后,远程对象和本地对象通过transact方法通信(由Binder驱动实现)
     */
    public static com.example.server.ISumServiceInterface asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      //判断是否有本地实现,有,则返回本地对象,不需要IPC
      if (((iin != null) && (iin instanceof com.example.server.ISumServiceInterface))) {
        return ((com.example.server.ISumServiceInterface) iin);
      }
      //没有,则生成并返回Proxy,即远程对象,然后,远程对象和本地对象通过transact方法通信(由Binder驱动实现)
      return new com.example.server.ISumServiceInterface.Stub.Proxy(obj);
    }

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

    /**
     * 这是本地对象接收数据的方法,当远程对象发起请求后,经过Binder驱动的封装处理,最终会调到server端的这个方法
     *
     * @param code  server端通过code来确定client端请求的目标方法,比如TRANSACTION_sum表示调用client端希望调用server端的sum方法
     * @param data  方法参数的封装,比如,data.readInt()得到参数a,再次调用data.readInt()得到参数b
     * @param reply 结果返回的地方,比如,sum方法得到a+b=100,就将100写入replay,replay通过Binder驱动的传送,返回给正在挂起等待结果的client端
     * @param flags 一般没什么卵用
     * @return
     * @throws android.os.RemoteException
     */
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code) {
        //client希望调用sum方法
        case TRANSACTION_sum: {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt(); //读取参数a
          int _arg1;
          _arg1 = data.readInt(); //读取参数b
          int _result = this.sum(_arg0, _arg1); //调用本地对象的sum方法,这个this很明白了吧,就是Server端在SumService的onBind()时实例化的那个Stub
          reply.writeNoException();
          reply.writeInt(_result); //结果写入reply,经Binder驱动传送给正在挂起等待的client
          return true;
        }
      }
      return true;
    }

    //Proxy是远程对象,通过transact向Binder驱动发送数据,并最终通过onTransact发送给Server端
    private static class Proxy implements com.example.server.ISumServiceInterface {
      private android.os.IBinder mRemote;

      /**
       * Proxy构造函数,client端调用
       *
       * @param remote 指明是哪个Binder的代理,这里的remote是Server端SumServcie的onBind()方法返回的IBinder,即Server端的Stub
       * @return
       */
      Proxy(android.os.IBinder remote) {
        mRemote = remote;
      }

      @Override
      public android.os.IBinder asBinder() {
        return mRemote;
      }

      public java.lang.String getInterfaceDescriptor() {
        return DESCRIPTOR;
      }

      @Override
      //client端调用远程对象的sum方法,最终会调到server端的sum方法
      public int sum(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); //指明向哪个Binder请求服务
          _data.writeInt(a);  //写入参数a
          _data.writeInt(b);  //写入参数b
          boolean _status = mRemote.transact(Stub.TRANSACTION_sum, _data, _reply, 0); //通过transact向server端发起TRANSACTION_sum请求,即sum方法
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().sum(a, b);
          }
          _reply.readException();
          _result = _reply.readInt(); //读取server端返回的结果,至此,一个完整的IPC通信结束。
        } finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

      public static com.example.server.ISumServiceInterface sDefaultImpl;
    }

    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_sum = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
  }
}

分析ISumServiceInterface.java类,实际上主要包含Stub和Proxy两个静态内部类。

一个完整的AIDL通信流程如下:

  1. Client端绑定Server端的SumService服务
  2. Server端的SumService返回IBinder,即ISumServiceInterface.Stub对象
  3. Client端调用ISumServiceInterface.Stub.onInterface(IBinder ibinder)方法,得到ISumServiceInterface.Stub.Proxy对象
  4. Client端调用Proxy的sum()方法,Proxy的sum()方法会调用transact()方法通过Binder驱动回调到Server端的onTransact()方法
  5. Client端挂起
  6. Server端通过onTransact()方法读取方法名和参数,进而回调本地Stub实例的sum方法
  7. Server端将sum()方法结果写入reply
  8. Client端读取reply结果

有两个需要注意的地方:

  1. 因为Client端调用Proxy的sum方法后会挂起,所以如果预计server端是耗时操作,Client端应该做好异步操作
  2. 因为Server端的onTransact()方法运行在Binder线程池,所以即使sum()方法是耗时操作,也不用另起线程

至此,AIDL的底层逻辑分析完毕!

 

推荐阅读