Android IPC 之AIDL应用(上)

1,710 阅读7分钟

前言

IPC 系列文章:
建议按顺序阅读。

Android IPC 之Service 还可以这么理解
Android IPC 之Binder基础
Android IPC 之Binder应用
Android IPC 之AIDL应用(上)
Android IPC 之AIDL应用(下)
Android IPC 之Messenger 原理及应用
Android IPC 之服务端回调
Android IPC 之获取服务(IBinder)
Android Binder 原理换个姿势就顿悟了(图文版)

上一篇文章分析了如何使用Binder进行进程间通信以及提出了直接使用Binder编码的缺点,本篇阐述如何使用AIDL解决以上缺点。
通过本篇文章,你将了解到:

1、如何编写AIDL 文件
2、如何使用AIDL

1、如何编写AIDL 文件

什么是AIDL

AIDL 是Android Interface Definition Language (Android 接口定义语言)的缩写。

创建AIDL 文件

Android Studio本身支持创建AIDL文件,先创建名为IMyServer 的AIDL文件。
在Module上右键单击:

image.png

输入名字:

image.png

确定后生成 IMyServer.aidl文件:

image.png

可以看出,由于是第一次创建AIDL文件,因此还创建了aidl文件夹并添加了包名作为目录结构,其总体结构如下:

src/main/aidl/com/fish/myapplication/IMyServer.aidl

其中/aidl目录与/java、/res目录平级,都在main目录下:

app/src/main/
app/src/aidl/
app/src/res

AIDL 文件内容

生成IMyServer.aidl内容如下:

//包名
package com.fish.myapplication;

interface IMyServer {
    //aidl 支持的基本数据类型
    //默认生成的方法,可以去掉
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

其中basicTypes(xx)方法是自动生成的,用来指导我们如何编写方法,可以去掉。
IMyServer 接口里声明的方法为Server端暴露给外部调用的方法,先为Server添加方法:


//包名
package com.fish.myapplication;

interface IMyServer {
    //只有一个参数,并且没有返回值
    void say(String word);
    
    //有两个参数,并且返回int
    int tell(String word, int age);
}

然后编译工程。

AIDL 编译产物

编译成功后,切换到Project模式,搜索IMyServer.java:

image.png

可以看出,编写的AIDL文件,最终根据一定的规则映射生成Java文件,接着来看看IMyServer.java内容。

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.fish.myapplication;
public interface IMyServer extends android.os.IInterface
{
  //默认类实现接口,可以不用关注
  public static class Default implements com.fish.myapplication.IMyServer
  {
    @Override public void say(java.lang.String word) throws android.os.RemoteException
    {
    }
    @Override public int tell(java.lang.String word, int age) throws android.os.RemoteException
    {
      return 0;
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }


  public static abstract class Stub extends android.os.Binder implements com.fish.myapplication.IMyServer
  {
    //描述符
    private static final java.lang.String DESCRIPTOR = "com.fish.myapplication.IMyServer";
    public Stub()
    {
      //调用Binder方法,将Binder与IInterface 关联起来
      //也就是说Binder持有IInterface引用
      this.attachInterface(this, DESCRIPTOR);
    }

    //通过Binder找到关联的IInterface
    public static com.fish.myapplication.IMyServer asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.fish.myapplication.IMyServer))) {
        //IBinder引用与调用者同一进程,直接返回IInterface
        return ((com.fish.myapplication.IMyServer)iin);
      }
      //不同进程则返回Proxy,并传入Binder
      return new com.fish.myapplication.IMyServer.Stub.Proxy(obj);
    }
    //返回自身
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    
    //重写onTransact(xx)
    @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)
      {
        //根据code,调用不同的方法
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_say:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          //反序列化,读取数据
          _arg0 = data.readString();
          this.say(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_tell:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          int _arg1;
          _arg1 = data.readInt();
          int _result = this.tell(_arg0, _arg1);
          reply.writeNoException();
          //写入回复
          reply.writeInt(_result);
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.fish.myapplication.IMyServer
    {
      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 void say(java.lang.String word) throws android.os.RemoteException
      {
        //构造序列化数据
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          //写入序列化
          _data.writeString(word);
          //mRemote为远程的IBinder
          boolean _status = mRemote.transact(Stub.TRANSACTION_say, _data, _reply, 0);
          //阻塞等待transact(xx)调用结果
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().say(word);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public int tell(java.lang.String word, int age) 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.writeString(word);
          _data.writeInt(age);
          boolean _status = mRemote.transact(Stub.TRANSACTION_tell, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().tell(word, age);
          }
          _reply.readException();
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.fish.myapplication.IMyServer sDefaultImpl;
    }
    static final int TRANSACTION_say = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_tell = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.fish.myapplication.IMyServer impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.fish.myapplication.IMyServer getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  
  //声明的公共方法
  public void say(java.lang.String word) throws android.os.RemoteException;
  public int tell(java.lang.String word, int age) throws android.os.RemoteException;
}

代码不长,但是初看起来云里雾里的,如果不怎么熟悉以上内容,强烈建议先阅读上篇文章:Android IPC 之Binder应用,再看这篇就很容易了。

提取重点分析上面的代码。
定义接口
定义了IMyServer 接口,该接口里的方法就是根据IMyServer.aidl里声明的方法生成的。

两个静态类
Stub是抽象类。
继承了Binder,重写了onTransact(xx)方法。
实现了IMyServer 接口,并没有实现里面的方法,这些方法待服务端实现。
当onTransact(xx)被调用的时候,根据不同的code调用相应的方法。
在上篇文章里分析的时候,onTransact(xx)与IMyServer 接口是分离的,我们需要手动在onTransact(xx)里调用IMyServer 方法。而此时Stub将两者结合起来了,完成了服务端与Binder驱动的联动。

Proxy虽然没有继承自Binder,但是持有IBinder引用:mRemote。
实现了IMyServer,并且实现了其所有方法,每个方法里最终都通过mRemote调用transact(xx)完成了客户端与Binder驱动联动。

至此,通过这两个类,分别完成了服务端、客户端与Binder的联动。
继续引用上篇的图:

image.png

可以看出,有了AIDL自动生成的类后:

1、繁杂的switch case 不用自己编写了
2、序列化反序列化也不用编写了
3、不再需要编写transact(xx)与onTransact(xx)了

极大解放了生产力。

2、如何使用AIDL

既然IMyServer.java 已经生成了,继续来看看如何使用它。

编写Server端业务

public class MyService extends Service {

    private final String TAG = "IPC";

    //构造内部类
    private IMyServer.Stub stub = new IMyServer.Stub() {
        @Override
        public void say(String word) throws RemoteException {
            Log.d(TAG, "receive say content:" + word + " in server");
        }

        @Override
        public int tell(String word, int age) throws RemoteException {
            Log.d(TAG, "receive tell content:" + word + " age:" + age + " in server");
            return age + 1;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //Stub 继承自Binder,因此是IBinder类型
        return stub;
    }
}

首先实现业务接口。
其次在onBind(xx)里将Binder返回给客户端。
业务逻辑实现了,等待客户端调用。

编写客户端业务

先定义ServiceConnection:

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyServer iMyServer = IMyServer.Stub.asInterface(service);
            try {
                iMyServer.say("how are you?");
                int result = iMyServer.tell("how are you?", 18);
                Log.d("IPC", "receive return content:" + result + " in client");
            } catch (Exception e) {

            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

service 为IBinder引用,该引用从服务端经过Binder驱动传递而来(不一定是同一个引用)。
IMyServer.Stub.asInterface(service) 用来寻找该IBinder对应的服务端提供的接口。

1、当IBinder与调用者同一进程,则IBinder为Binder类型,即为自身定义的Stub。
2、当IBinder与调用者不是同一进程,则IBinder为BinderProxy类型(为什么是这个类型,后续文章会分析)。

此处测试的是两个不同的进程,因此IBinder service指向BinderProxy。
在ServiceConnection里,当绑定成功后调用Proxy里的方法,其内部通过BinderProxy调用transact(xx)。

上面的逻辑都写了,最后当然需要绑定Service:

    private void bindService() {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }

来看看打印结果:

image.png

可以看出通信成功了。

需要注意的是:

以上Demo都是同一个工程里编写的,因此客户端、服务端都能访问IMyServer.Stub,若是在不同的进程,需要写同样的IMyServer.aidl文件。

让Service在不同的进程运行只需要在AndroidManifest.xml添加如下字段:

        <service android:name=".MyService" android:process=":aidl">
        </service>

绑定后,运行的两个进程如下:

image.png

总结AIDL用法

通过以上Demo可以看出,通过编写AIDL实现IPC。
服务端仅需要两步:
第一步
实现接口对应方法的业务逻辑

第二步
在onBind(xx)里将接口关联的Binder返回

同样的客户端调用服务端仅仅只需要两步:
第一步
通过Stub拿到服务端的接口

第二步
拿到接口后调用对应的方法

image.png

明显的,IMyServer.java 已经为我们实现了连接Binder的功能,屏蔽了对接的Binder细节。客户端调用服务端的方法(Proxy)与服务端进行通信,就像是"直接"调用一般,符合Java一贯的面向对象的思维。

结合上一篇文章对Binder应用的分析,以及本篇AIDL的分析,我们知道:

  • AIDL 并不是我们熟知的Java、C++语言,而是一种规范。按此规范编写的AIDL文件最终生成对应的.java文件,该文件里实现了客户端调用transact(xx)以及调用服务端的onTransact(xx),通过.java文件就能实现进程间通信。
  • .java文件里将工作分为了两部分:一是客户端的逻辑封装在Proxy里,而服务端的逻辑封装在Stub里,典型的Proxy-Stub(代理-桩)模式。
  • 进程间通信的核心是Binder,AIDL本身并不能实现进程间通信,仅仅是简化了编码的流程。

接下来将重点分析AIDL 传递自定义数据类型以及定向Tag相关问题。

本文基于Android 10.0。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营学习Android