使用AIDL进行进程间通信

学习了一些Binder相关的进程间通信知识,但是仅为是了解了理论,在Android实战中,如何用代码来实现,今天就带大家一起来总结领略一下AIDL的风采。

AIDL是什么

AIDL(Android Interface Definition Language)是Android提供的一种进程间通信(IPC)机制。

我们可以利用它,定义客户端与服务端之间进行进程间通信时双方都认可的编程接口。

在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。

编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。

通过这种机制,我们只需要写好 aidl 接口文件,编译时系统会帮我们生成 Binder 接口。

AIDL使用场景

虽然AIDL作用很大,但并不是所有情况下都是最好的跨进程通信方案,那么什么场景下才适合使用呢?对此,官方给出的解释是:“只有当你允许来自不同应用程序的客户端远程访问你的服务并且在你的服务中处理多线程的情况下才使用AIDL;如果不需要跨应用程序实现并发进程间通信的话,那么你应该使用Binder接口;如果只是进程间通信,但不许处理多线程,那么使用Messenger。”


AIDL支持的数据类型

  1. AIDL支持的基本数据类型 (int、long、boolean、float、double、char)、String和CharSequence,这些不需要import 导入。

  2. List 和 Map,不需要import 导入
    1.1 元素必须是AIDL支持的数据类型
    1.2 Server端具体的类里则必须是ArrayList或者HashMap

  3. 其他AIDL生成的接口,需要import,即使是在相同包结构下

  4. 实现Parcelable序列化的类,需要import

需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。

AIDL只支持接口方法,不能公开static变量。

AIDL的编写

  1. 创建AIDL
    1.1 创建要操作的实体类,实现Parcelable接口,使其具有序列化/反序列化能力
    1.2 新建aidl文件夹,在其中创建接口aidl文件以及实体类的映射aidl文件
    1.3 make project,生成Binde的Java文件

  2. 服务端
    2.1 创建Service,在其中创建上面生成的Binder对象的实例,实现接口定义的方法
    2.2 在onBind()中返回

  3. 客户端
    3.1 实现ServiceConnection接口,在其中拿到AIDL类
    3.2 bindService()
    3.3 调用AIDL类中定义好的操作请求

AIDL实例

1. 创建AIDL

1.1 创建要操作的实体类,实现Parcelable接口,使其具有序列化/反序列化能力

package com.bard.gplearning.aidl.aidlbean;

import android.os.Parcel;
import android.os.Parcelable;

public class Person implements Parcelable {
    private int age;
    private String name;

    protected Person(Parcel in) {
        age = in.readInt();
        name = in.readString();
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(age);
        dest.writeString(name);
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

1.2 新建aidl文件夹,在其中创建接口aidl文件以及实体类的映射aidl文件
在 main 文件夹下新建 aidl 文件夹,使用的包名要和 java 文件夹的包名一致:

1.2.1 先创建实体类的映射 aidl 文件,Person.aidl:

// Person.aidl
package com.bard.gplearning.aidl.aidlbean;

//还要和声明的实体类在一个包里
parcelable Person;

在其中声明映射的实体类名称与类型
注意,这个 Person.aidl 的包名要和实体类包名一致。

1.2.2 然后创建接口 aidl 文件,IMyAidl.aidl(自行创建即可,不不需使用AS提供的生成功能):

// IMyAidlInterface.aidl
package com.bard.gplearning;

// Declare any non-default types here with import statements
import com.bard.gplearning.aidl.aidlbean.Person;

interface IMyAidlInterface {
     /**
      * 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
      */
      void addPerson(in Person person);

      List<Person> getPersonList();
}

在接口 aidl 文件中定义将来要在跨进程进行的操作,上面的接口中定义了两个操作:

  • addPerson: 添加 Person
  • getPersonList:获取 Person 列表

需要注意的是:

  • 非基本类型的数据需要导入,比如上面的 Person,需要导入它的全路径。
    • 这里的 Person 我理解的是 Person.aidl,然后通过 Person.aidl 又找到真正的实体 Person 类。
  • 方法参数中,除了基本数据类型,其他类型的参数都需要标上方向类型
    • in(输入), out(输出), inout(输入输出)

1.3 make project,生成Binde的Java文件

AIDL 真正的强大之处就在这里,通过简单的定义 aidl 接口,然后编译,就会为我们生成复杂的 Java 文件。

点击 Build -> Make Project,然后等待构建完成。

然后就会在 build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/你的package dir/ 下生成一个 Java 文件:

具体代码内容如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/penggu/workplace/github/GPLearning/app/src/main/aidl/com/bard/gplearning/IMyAidlInterface.aidl
 */
package com.bard.gplearning;

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

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

        /**
         * Cast an IBinder object into an com.bard.gplearning.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.bard.gplearning.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.bard.gplearning.IMyAidlInterface))) {
                return ((com.bard.gplearning.IMyAidlInterface) iin);
            }
            return new com.bard.gplearning.IMyAidlInterface.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 {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_addPerson: {
                    data.enforceInterface(descriptor);
                    com.bard.gplearning.aidl.aidlbean.Person _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.bard.gplearning.aidl.aidlbean.Person.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addPerson(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getPersonList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.bard.gplearning.aidl.aidlbean.Person> _result = this.getPersonList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.bard.gplearning.IMyAidlInterface {
            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;
            }

            /**
             * 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
             */
            @Override
            public void addPerson(com.bard.gplearning.aidl.aidlbean.Person person) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((person != null)) {
                        _data.writeInt(1);
                        person.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.util.List<com.bard.gplearning.aidl.aidlbean.Person> getPersonList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.bard.gplearning.aidl.aidlbean.Person> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.bard.gplearning.aidl.aidlbean.Person.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

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

    /**
     * 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
     */
    public void addPerson(com.bard.gplearning.aidl.aidlbean.Person person) throws android.os.RemoteException;

    public java.util.List<com.bard.gplearning.aidl.aidlbean.Person> getPersonList() throws android.os.RemoteException;
}


现在我们有了跨进程 Client 和 Server 的通信媒介,接着就可以编写客户端和服务端代码了。

2. 编写服务端代码

2.1 创建Service,在其中创建上面生成的Binder对象的实例,实现接口定义的方法。
2.2 在onBind()中返回
创建将来要运行在另一个进程的 Service,在其中实现了 AIDL 接口中定义的方法:

public class MyAidlService extends Service {

    /**
     * 如果是两个project,这个class应在服务端,aidl文件两端各一份
     *
     * 客户端与服务端绑定时的回调,返回 mIBinder 后客户端就可以通过它远程调用服务端的方法,即实现了通讯
     * @param intent
     * @return
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        mPersons = new ArrayList<>();
        Log.d(TAG, "MyAidlService onBind");
        return mIBinder;
    }


    private final String TAG = this.getClass().getSimpleName();

    private ArrayList<Person> mPersons;

    /**
     * 创建生成的本地 Binder 对象,实现 AIDL 制定的方法
     */
    private IBinder mIBinder = new IMyAidlInterface.Stub() {

        @Override
        public void addPerson(Person person) throws RemoteException {
            mPersons.add(person);
        }

        @Override
        public List<Person> getPersonList() throws RemoteException {
            return mPersons;
        }
    };
}

上面的代码中,创建的对象是一个 IMyAidl.Stub() ,它是一个 Binder,具体为什么是它我们下篇文章介绍。

别忘记在 Manifest 文件中声明:

<service
  android:name="com.bard.gplearning.aidl.service.MyAidlService"
    android:enabled="true"
    // enabled 是否可以被系统实例化,
    // 默认为 true 因为父标签 也有 enable 属性,
    // 所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。
    android:exported="true"
    // exported 是否支持其它应用调用当前组件。 
    // 默认值:如果包含有intent-filter 默认值为true;
    // 没有intent-filter默认值为false。
    android:process=":aidl">

    <intent-filter>
                //该Service可以响应带有com.bard.gplearning.IMyAidlInterface这个action的Intent。
                //此处Intent的action必须写成“服务器端包名.aidl文件名”
                <action android:name="com.bard.gplearning.IMyAidlInterface" />
            </intent-filter>
</service>

注意:
1、action 中的 name 需写成 【服务器端包名.aidl文件名】
2、android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有【“:”分号】的,则创建全局进程,不同的应用程序共享该进程。
3、设置了 android:process 属性将组件运行到另一个进程,相当于另一个应用程序,所以在另一个线程中也将新建一个 Application 的实例。因此,每新建一个进程 Application 的 onCreate 都将被调用一次。 如果在 Application 的 onCreate 中有许多初始化工作并且需要根据进程来区分的,那就需要特别注意了。

服务端实现了接口,在 onBind() 中返回这个 Binder,客户端拿到就可以操作数据了。

3.编写客户端代码

这里我们以一个 Activity 为客户端。
3.1 实现ServiceConnection接口,在其中拿到AIDL类

private IMyAidlInterface mAidl;

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //连接后拿到 Binder,转换成 AIDL,在不同进程会返回个代理
        mAidl = IMyAidlInterface.Stub.asInterface(service);
    }

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

在 Activity 中创建一个服务连接对象,在其中调用 IMyAidlInterface.Stub.asInterface() 方法将 Binder 转为 AIDL 类。

3.2 接着绑定服务bindService()

//如果是两个project,则客户端需用隐式调用,setPackage、setAction等
Intent intent1 = new Intent(getApplicationContext(), MyAidlService.class);
bindService(intent1, mConnection, BIND_AUTO_CREATE);

要执行 IPC,必须使用 bindService() 将应用绑定到服务上。

注意:
5.0 以后要求显式调用 Service,所以我们无法通过 action 或者 filter 的形式调用 Service,具体内容可以看这篇文章 Android 进阶:Service 的一些细节

3.3 拿到AIDL类之后,就可以调用AIDL类中定义好的操作,进行跨进程请求

@OnClick(R.id.btn_add_person)
public void addPerson() {
    Random random = new Random();
    Person person = new Person("Andy_" + random.nextInt(10));

    try {
        mAidl.addPerson(person);
        List<Person> personList = mAidl.getPersonList();
        mTvResult.setText(personList.toString());
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

这样,Activity 与 另外一个进程的 Service 通信成功了。

解析生成的IMyAidlInterface.java

public interface IMyAidlInterface extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.bard.gplearning.IMyAidlInterface {
        //唯一标识,一般为完整路径
        private static final java.lang.String DESCRIPTOR = "com.bard.gplearning.IMyAidlInterface";

        //构造函数,创建生成的本地 Binder 对象时候使用
        //创建后会让你实现对应的接口方法【addPerson,getPersonList】
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * 将一个 IBinder 转换为 IMyAidl,如果不在一个进程就创建一个代理
         * 用在创建的 ServiceConnection 的回调 onServiceConnected 中
         * mAidl = IMyAidlInterface.Stub.asInterface(service);
         */
        public static com.bard.gplearning.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.bard.gplearning.IMyAidlInterface))) {
                return ((com.bard.gplearning.IMyAidlInterface) iin);
            }
            return new com.bard.gplearning.IMyAidlInterface.Stub.Proxy(obj);
        }
        
         //覆盖 IInterface 的方法,获取当前接口对应的 Binder 对象
        @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 {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                //获取当前接口的描述符
                case INTERFACE_TRANSACTION: {  
                    reply.writeString(descriptor);
                    return true;
                }
                //执行 addPerson 方法
                case TRANSACTION_addPerson: {
                    data.enforceInterface(descriptor);
                    com.bard.gplearning.aidl.aidlbean.Person _arg0;
                    if ((0 != data.readInt())) {    //反序列化传入的数据
                        _arg0 = com.bard.gplearning.aidl.aidlbean.Person.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    //调用 addPerson 方法,这个方法的实现是在服务端
                    this.addPerson(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getPersonList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.bard.gplearning.aidl.aidlbean.Person> _result = this.getPersonList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }


        //不在一个进程时返回的代理
        private static class Proxy implements com.bard.gplearning.IMyAidlInterface {
            ...
        }

         //用于 onTransact 方法的两个 code,分别标识要进行的操作
        static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }


    public void addPerson(com.bard.gplearning.aidl.aidlbean.Person person) throws android.os.RemoteException;

    public java.util.List<com.bard.gplearning.aidl.aidlbean.Person> getPersonList() throws android.os.RemoteException;
}

可以看到:

  1. 生成的接口 IMyAidlInterface 继承了 IInterface
    Android 进阶8:进程通信之 Binder 机制浅析 中我们介绍了,IInterface 是进程间通信定义的通用接口。
  2. IMyAidlInterface 中包含了我们在 aidl 文件中声明的两个方法。
  3. IMyAidlInterface 中还包括一个抽象类 Stub,它是一个 Binder,实现了 IMyAidl 接口,有几个重要方法【构造方法、asInterface、asBinder、onTransact】和几个code【用来在onTransact标识要进行的操作】
  4. Stub类中有一个静态内部类 Proxy,是不在一个进程时返回的代理
Stub 的几个关键内容介绍:
  1. 构造函数
    1.1 调用了 attachInterface() 方法
    1.2 将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个
    1.3 需要创建一个 DESCRIPTOR,一般是类的具体路径名,用于唯一表示这个 IInterface
  2. asInterface()
    2.1 将 IBinder 转换为 IMyAidlInterface ,这用于返回给客户端
    2.2 不在一个进程的话,客户端持有的是一个代理
  3. onTransact()
    3.1 Binder 关键的处理事物方法
    3.2 根据传入的 code,调用本地/服务端的不同方法
Proxy

是一个实现了IMyAidlInterface接口的静态内部类,结构与Stub类似,具体如下:

AIDL生成小结

  • IInterface 类型的接口,包括:
    • Stub 抽象类
    • aidl 接口定义的操作方法
  • Stub ,是一个 Binder,同时也是一个 IInterface,包括:
    • 将 Binder 转成 IInterface 的 asInterface() 方法
    • 处理调度的 onTransact() 方法
    • 用于在 onTransact() 中标识要进行的操作的两个标志
    • 一个 IInterface 类型的代理
  • Proxy, IInterface 类型的代理,包括:
    • 接口定义方法的伪实现,实际调用的是真正的 Binder 的方法

总结

这篇文章介绍了 AIDL 的简单编写流程,其中也踩过一些坑,比如文件所在包的路径不统一,绑定服务收不到回调等问题。Binder还是个内容庞大的体系架构,本文也只算是浅尝辄止,后续还要更深入的研究。

推荐阅读更多精彩内容