读完本文你将了解:

在 Android 进阶7:进程通信之 AIDL 的使用 中我们使用 AIDL 实现了跨进程的通信,但是不清楚 AIDL 帮我们做了什么。

AIDL 的本质是简化我们 IPC 开发,它使用的是 Binder 机制,于是在上篇文章 Android 进阶8:进程通信之 Binder 机制浅析 中我们简单了解了 Binder 机制的流程。

有了前面的铺垫,这篇文章我们就一起来看看 AIDL 究竟帮我们做了什么。

AIDL

前面我们讲了,在使用 AIDL 编写 IPC 代码时,我们只需要编写简单的 接口 aidl 文件:

Make Project 后系统就会帮我们生成 Java 文件:

AIDL 生成文件分析

AIDL 帮我们生成内容:

代码如下:

可以看到,生成的接口 IMyAidl 继承了 IInterface,Android 进阶8:进程通信之 Binder 机制浅析 中我们介绍了,IInterface 是进程间通信定义的通用接口。

同时 IMyAidl 中也包含了我们在 aidl 文件中声明的两个方法。

除此外,IMyAidl 中还包括一个抽象类 Stub,它是一个 Binder,实现了 IMyAidl 接口:

Stub

代码如下:

public static abstract class Stub extends android.os.Binder implements net.sxkeji.shixinandroiddemo2.IMyAidl {
    //唯一标识,一般为完整路径
        private static final java.lang.String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.IMyAidl";

    /**
     * 将当前接口与 Binder 绑定
     */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * 将一个 IBinder 转换为 IMyAidl,如果不在一个进程就创建一个代理
     */
    public static net.sxkeji.shixinandroiddemo2.IMyAidl asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        //拿着标识从本地查询接口
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof net.sxkeji.shixinandroiddemo2.IMyAidl))) {
            return ((net.sxkeji.shixinandroiddemo2.IMyAidl) iin);
        }
        //查不到就返回代理
        return new net.sxkeji.shixinandroiddemo2.IMyAidl.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 {
        switch (code) {
            case INTERFACE_TRANSACTION: {    //获取当前接口的描述符
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_addPerson: {    //执行 addPerson 方法
                data.enforceInterface(DESCRIPTOR);
                net.sxkeji.shixinandroiddemo2.bean.Person _arg0;
                if ((0 != data.readInt())) {        //反序列化传入的数据
                    _arg0 = net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                //调用 addPerson 方法,这个方法的实现是在服务端
                this.addPerson(_arg0);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_getPersonList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result = this.getPersonList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    //不在一个进程时返回的代理
    private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {...}

    //用于 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);
}

Stub 的几个关键内容介绍:

  1. 构造函数

    • 调用了 attachInterface() 方法
    • 将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个
    • 需要创建一个 DESCRIPTOR,一般是类的具体路径名,用于唯一表示这个 IInterface
  2. asInterface()
    • 将 IBinder 转换为 IMyAidl ,这用于返回给客户端
    • 不在一个进程的话,客户端持有的是一个代理
  3. onTransact()
    • Binder 关键的处理事物方法
    • 根据传入的 code,调用本地/服务端的不同方法

其中可以看到,在不同进程时返给客户端的是代理。

Proxy

代码:

private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {
    private android.os.IBinder mRemote;    //代理的远端 IBinder

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    //获取代理的 Binder
    @Override
    public android.os.IBinder asBinder() {
        return mRemote;
    }

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

    /**
     * 代理嘛,处理下数据后直接调用实际 Binder 来处理
     */
    @Override
    public void addPerson(net.sxkeji.shixinandroiddemo2.bean.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<net.sxkeji.shixinandroiddemo2.bean.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<net.sxkeji.shixinandroiddemo2.bean.Person> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

AIDL 生成的内容小结

  • IInterface 类型的接口,包括:

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

一句话总结:AIDL 帮我们生成了 Binder 和 跨平台接口的转换类 Stub,以及在不同进程时,客户端拿到的代理 Proxy。

现在回去看一下 AIDL 的使用,就会多了些理解。

AIDL 的使用回顾

服务端

使用时先在另一个进程的 Service 中实现 AIDL 生成文件中的 Stub 类,然后在 onBind() 中返回:

结合上面的分析,可以知道,我们在服务端实例化的是 Stub 的实体,它既是 Binder 也是 IInterface。在其中实现了接口定义的方法,然后在 onBind() 中返回自己。

客户端

在 Activity 中使用 bindService() 绑定服务,然后再回调中,调用 Stub.asInterface() 将拿到的远端 Binder 转换为定义的接口,跨进程的话这里拿到的实际是代理接口:

然后就可以调用 Service 中方法了。

小结

根据上面的分析,我们可以看到,AIDL 帮我们做了以下几件事:

  1. 根据定好的接口生成不同进程都可以共同访问的接口类
  2. 在接口类中提供了 Binder 和接口的共同载体 Stub
  3. 在 Stub 中创建了代理类,用于映射调用实际接口实现

有了 AIDL,我们编写跨进程操作就变得十分简单,我们只需要关注业务接口的实现即可。

手动写个 Binder

我们可以模仿 AIDL 创建的文件,手动写个 Binder 来加深下理解。

首先是定义跨进程接口,实现 IInterface

在其中定义要跨进程做的操作,以及标识这两个操作的 code:

public interface IMyAidlDiy extends IInterface {
    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(Person person) throws RemoteException;

    public List<Person> getPersonList() throws RemoteException;
}

然后在其中创建这个接口与对应 Binder 的转换类 Stub

既然要两头讨好,那它就需要继承 Binder 的同时实现前面定义的接口,同时提供 Binder 和接口转换的方法,以及作为接口处理事物的方法:

public static abstract class Stub extends Binder implements IMyAidlDiy {

    private static final String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.activity.ipc.IMyAidlDiy";

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

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

    public static IMyAidlDiy asInterface(IBinder binder){
        if (binder == null){
            return null;
        }

        IInterface localInterface = binder.queryLocalInterface(DESCRIPTOR);
        if (localInterface != null && localInterface instanceof IMyAidlDiy){
            return (IMyAidlDiy) localInterface;
        }else {
            return new Stub.Proxy(localInterface);
        }
    }

    @Override
    protected boolean onTransact(final int code, final Parcel data, final Parcel reply, final int flags) throws RemoteException {
        switch (code){
            case TRANSACTION_addPerson:
                data.enforceInterface(DESCRIPTOR);
                Person _arg0;
                if (data.readInt() != 0){
                    _arg0 = Person.CREATOR.createFromParcel(data);  //反序列化参数
                }else {
                    _arg0 = null;
                }
                this.addPerson(_arg0);
                reply.writeNoException();
                return true;
            case TRANSACTION_getPersonList:
                data.enforceInterface(DESCRIPTOR);
                List<Person> personList = this.getPersonList();
                reply.writeNoException();
                reply.writeTypedList(personList);
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

}

最后创建代理接口,在不同进程中,客户端持有的是代理

它的作用就是伪装成真的 Binder,实际被调用时将数据处理成 Parcel,然后让被代理的 Binder 去处理:

private static class Proxy implements IMyAidlDiy {
    private IBinder mRemote;

    public Proxy(final IBinder obj) {
        mRemote = obj;
    }

    public java.lang.String getInterfaceDescriptor() {  //伪装的和真的 Binder 名一样
        return DESCRIPTOR;
    }

    @Override
    public void addPerson(final Person person) throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();

        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if (person != null) {
                _data.writeInt(1);
                person.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(TRANSACTION_addPerson, _data, _reply, 0);  //这里调用实际的实现
            _reply.readException();

        } finally {
            _data.recycle();
            _reply.recycle();
        }
    }

    @Override
    public List<Person> getPersonList() throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();
        List<Person> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(TRANSACTION_getPersonList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(Person.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

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

自己写完实现对整个 Binder 实现的跨进程调用流程是否理解更深了呢,这里用《Android 开发艺术探索》的一张图总结下:

需要注意的是,客户端在发起远程请求时,当前线程会被挂起直到服务端返回,因此尽量不要在 UI 线程发起远程请求。

而在服务端,Binder 方法是运行在 Binder 线程池中的,因此可以直接使用同步的方式实现。

Thanks

《Android 开发艺术探索》

http://weishu.me/2016/01/12/binder-index-for-newer/

Android 进阶9:进程通信之 AIDL 解析的更多相关文章

  1. 图文详解 Android Binder跨进程通信机制 原理

    图文详解 Android Binder跨进程通信机制 原理 目录 目录 1. Binder到底是什么? 中文即 粘合剂,意思为粘合了两个不同的进程 网上有很多对Binder的定义,但都说不清楚:Bin ...

  2. Android 进阶7:进程通信之 AIDL 的使用

    读完本文你将了解: AIDL 是什么 AIDL 支持的数据类型 AIDL 如何编写 AIDL 实例 创建 AIDL 编写服务端代码 编写客户端代码 运行结果 总结 代码地址 Thanks 记得 201 ...

  3. Android上的进程通信(IPC)机制

    Interprocess Communication Android offers a mechanism for interprocess communication (IPC) using rem ...

  4. Android 进阶13:几种进程通信方式的对比总结

    不花时间打基础,你将会花更多时间解决那些不必要的问题. 读完本文你将了解: RPC 是什么 IDL 是什么 IPC 是什么 Android 几种进程通信方式 如何选择这几种通信方式 Thanks RP ...

  5. Android 进阶8:进程通信之 Binder 机制浅析

    读完本文你将了解: IBinder Binder Binder 通信机制 Binder 驱动 Service Manager Binder 机制跨进程通信流程 Binder 机制的优点 总结 Than ...

  6. 详解 CmProcess 跨进程通信的实现

    CmProcess 是 Android 一个跨进程通信框架,整体代码比较简单,总共 20 多个类,能够很好的便于我们去了解跨进程实现的原理. 个人猜测 CmProcess 也是借鉴了 VirtualA ...

  7. Android 进阶10:进程通信之 Messenger 使用与解析

    读完本文你将了解: Messenger 简介 Messenger 的使用 服务端 客户端 运行效果 使用小结 总结 代码地址 Thanks 前面我们介绍了 AIDL 的使用与原理,这篇文章来介绍下 A ...

  8. Android 进阶11:进程通信之 ContentProvider 内容提供者

    学习启舰大神,每篇文章写一句励志的话,与大家共勉. When you are content to be simply yourself and don't compare or compete, e ...

  9. Android学习笔记_23_服务Service之AIDL和远程服务实现进程通信以及进程间传递自定义类型参数

    一.了解AIDL语言: 在Android中, 每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,该如何实现呢? 显然, Java中是不支持跨进程内存共享的.因此要传递对象, 需要把对象解析 ...

随机推荐

  1. mongo常用查询

    复杂查询: and: or: lte,gte,=: and+lt:  , 逗号表示and, $lt小于写在值当中 查询实例: 找到含有指定数据文档 查找条件spcode有1个字符长度的文档 db.sp ...

  2. 调色盘canvas

    //调色盘 function draw8(id){ var canvas = document.getElementById(id); var context = canvas.getContext( ...

  3. Java集合(8):Hashtable

    一.Hashtable介绍 和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射,它在很大程度上和HashMap的实现差不多. Hashtable ...

  4. 12 Spring框架 SpringDAO的事务管理

    上一节我们说过Spring对DAO的两个支持分为两个知识点,一个是jdbc模板,另一个是事务管理. 事务是数据库中的概念,但是在一般情况下我们需要将事务提到业务层次,这样能够使得业务具有事务的特性,来 ...

  5. sql 转

  6. 剑指offer编程题66道题 26-35

    26.二叉搜索树与双向链表 题目描述 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建任何新的结点,只能调整树中结点指针的指向.   中序遍历思路:按照右中左的顺序,中序遍历对 ...

  7. CAS单点登出的原理

    单点登出功能跟单点登录功能是相对应的,旨在通过Cas Server的登出使所有的Cas Client都登出. Cas Server的登出是通过请求“/logout”发生的,即如果你的Cas Serve ...

  8. rest_framework解析器组件源码流程

    rest_framework解析器组件源码流程 解析器顾名思义就是对请求体进行解析.为什么要有解析器?原因很简单,当后台和前端进行交互的时候数据类型不一定都是表单数据或者json,当然也有其他类型的数 ...

  9. 轮询、长轮询、长连接、socket连接、WebSocket

    轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接. 优点:后端程序编写比较容易. 缺点:请求中有大半是无用,浪费带宽和服务器资源.(而每一次的 HTTP 请求和应答 ...

  10. computeIfAbsent

    // java8之前,若从map中根据key获取value,如果key不存在,则添加,这一系列操作可以是下面的操作 Object key = map.get("key"); if ...