美文网首页Android开发经验谈Android开发Android开发
Android进阶 之进程通信之 AIDL 的使用

Android进阶 之进程通信之 AIDL 的使用

作者: 881ef7b85f62 | 来源:发表于2019-01-21 20:56 被阅读6次

    AIDL 是什么

    AIDL(Android 接口定义语言) 是 Android 提供的一种进程间通信 (IPC) 机制。

    我们可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。

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

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

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

    AIDL 支持的数据类型

    共 4 种:

    1. Java 的基本数据类型
    2. List 和 Map
      • 元素必须是 AIDL 支持的数据类型
      • Server 端具体的类里则必须是 ArrayList 或者 HashMap
    3. 其他 AIDL 生成的接口
    4. 实现 Parcelable 的实体

    AIDL 如何编写

    AIDL 的编写主要为以下三部分:

    1. 创建 AIDL
      • 创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化
      • 新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件
      • Make project ,生成 Binder 的 Java 文件
    2. 服务端
      • 创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法
      • onBind() 中返回
    3. 客户端
      • 实现 ServiceConnection 接口,在其中拿到 AIDL 类
      • bindService()
      • 调用 AIDL 类中定义好的操作请求

    AIDL 实例

    下面以实例代码演示一个 AIDL 的编写。

    1.创建 AIDL

    ①创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化

    package net.sxkeji.shixinandroiddemo2.bean;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Person implements Parcelable {
        private String mName;
    
        public Person(String name) {
            mName = name;
        }
    
        protected Person(Parcel in) {
            mName = 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.writeString(mName);
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "mName='" + mName + '\'' +
                    '}';
        }
    }
    

    实现 Parcelable 接口是为了后序跨进程通信时使用。

    关于 Parcelable 可以看我的这篇文章 Android 进阶6:两种序列化方式 Serializable 和 Parcelable

    注意 实体类所在的包名。

    ②新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件

    在 main 文件夹下新建 aidl 文件夹,使用的包名要和 java 文件夹的包名一致:

    image

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

    // Person.aidl
    package net.sxkeji.shixinandroiddemo2.bean;
    
    //还要和声明的实体类在一个包里
    parcelable Person;
    

    在其中声明映射的实体类名称与类型

    注意,这个 Person.aidl 的包名要和实体类包名一致。

    然后创建接口 aidl 文件,IMyAidl.aidl:

    // IMyAidl.aidl
    package net.sxkeji.shixinandroiddemo2;
    
    // Declare any non-default types here with import statements
    import net.sxkeji.shixinandroiddemo2.bean.Person;
    
    interface IMyAidl {
        /**
         * 除了基本数据类型,其他类型的参数都需要标上方向类型: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(输入输出)

    ③Make Project ,生成 Binder 的 Java 文件

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

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

    然后就会在 build/generated/source/aidl/你的 flavor/ 下生成一个 Java 文件:

    image

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

    我们先跑通整个过程,这个文件的内容下篇文章介绍。

    2.编写服务端代码

    创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法;然后在 onBind() 中返回

    创建将来要运行在另一个进程的 Service,在其中实现了 AIDL 接口中定义的方法:

    public class MyAidlService extends Service {
        private final String TAG = this.getClass().getSimpleName();
    
        private ArrayList<Person> mPersons;
    
        /**
         * 创建生成的本地 Binder 对象,实现 AIDL 制定的方法
         */
        private IBinder mIBinder = new IMyAidl.Stub() {
    
            @Override
            public void addPerson(Person person) throws RemoteException {
                mPersons.add(person);
            }
    
            @Override
            public List<Person> getPersonList() throws RemoteException {
                return mPersons;
            }
        };
    
        /**
         * 客户端与服务端绑定时的回调,返回 mIBinder 后客户端就可以通过它远程调用服务端的方法,即实现了通讯
         * @param intent
         * @return
         */
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            mPersons = new ArrayList<>();
            LogUtils.d(TAG, "MyAidlService onBind");
            return mIBinder;
        }
    }
    

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

    别忘记在 Manifest 文件中声明:

    <service
        android:name="net.sxkeji.shixinandroiddemo2.service.MyAidlService"
        android:enabled="true"
        android:exported="true"
        android:process=":aidl"/>
    

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

    3.编写客户端代码

    这里我们以一个 Activity 为客户端。

    ①实现 ServiceConnection 接口,在其中拿到 AIDL 类

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

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

    ②接着绑定服务

    Intent intent1 = new Intent(getApplicationContext(), MyAidlService.class);
    bindService(intent1, mConnection, BIND_AUTO_CREATE);
    

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

    注意:

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

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

    @OnClick(R.id.btn_add_person)
    public void addPerson() {
        Random random = new Random();
        Person person = new Person("shixin" + random.nextInt(10));
    
        try {
            mAidl.addPerson(person);
            List<Person> personList = mAidl.getPersonList();
            mTvResult.setText(personList.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    

    运行结果

    [图片上传中...(image-1f7858-1548074843359-0)]

    可以看到,Activity 与 另外一个进程的 Service 通信成功了。

    总结

    这篇文章介绍了 AIDL 的简单编写流程,其中也踩过一些坑,比如文件所在包的路径不统一,绑定服务收不到回调等问题。

    到最后虽然跨进程通信成功,但是我们还是有很多疑问的,比如:

    • AIDL 生成的文件内容?
    • 什么是 Binder?
    • 为什么要这么写?

    知其然还要知其所以然,这一切都要从 Binder 讲起,且听下一回合介绍。

    代码地址

    喜欢的话请帮忙转发一下能让更多有需要的人看到吧,有些技术上的问题大家可以多探讨一下。

    以上Android资料以及更多Android相关资料及面试经验可在QQ群里获取:936903570。有加群的朋友请记得备注上简书,谢谢。

    相关文章

      网友评论

        本文标题:Android进阶 之进程通信之 AIDL 的使用

        本文链接:https://www.haomeiwen.com/subject/qtmhjqtx.html