Android AIDL详解

AIDL简介


AIDL是对以 Binder 为核心的跨进程通信机制的进一步封装,使得这个过程更加简化,只暴露出非常必要的开发者需要实现的接口,让开发人员实现业务逻辑。顾名思义,Android 接口定义语言。
既然还是以针对跨进程访问,不过就是客户端进程和服务端服务进程,且最好的使用场景是客户端进程和服务端进程不在同一个进程中。而访问和通信基础实际上来自于 Binder 机制。下面,首先我们针对上面这些语言中出现的关键词进行解析,即客户端进程,服务进程,Binder。

客户端进程

客户端,我们都知道这是一个相对于服务端而言的概念。
客户端通常并不处理具体逻辑,而只是请求向服务端请求结果,然后对结果进行处理。即,向服务端发出request,然后有服务端进行业务逻辑处理,然后返回给客户端response,客户端拿到response,做进一步处理(比如UI展示等)。
在Android应用开发过程中,客户端可以是任何组件,我们常用的activity,service(相对于另外一个service而言,它就是一个客户端)等。

服务端进程

说到服务端,下意识会想到服务器。服务端的优点在于,拥有强大的业务处理能力,实现了业务处理的集中管理,同时还可能增加更多的安全屏障。
在Android应用开发中,各种**ServiceManager都是服务端进程,或者我们实现的Service,也可以作为服务端进程。
说到Service,提一点题外话,来自于官网对Service的说明:

Service(服务)基本上分为两种形式:
启动
当应用组件(如 Activity)通过调用 startService()
启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后,服务会自行停止运行。
绑定
当应用组件通过调用 bindService()
绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

通过上面,我们应该可以看到 bindService 的优势和使用场景,这也就是为什么有时候我们需要定义客户端,定义服务端,主要还是便于交互啊。当然,你说我不使用这种方式,就不能实现和服务的交互了吗?当然不是啊,实现方式肯定不止一种,但是这种方式无疑是最方便的吧。

IBinder

根据官方文档[译]:

接口IBinder 是轻量级高性能远程调用机制的核心部分。
该接口描述了和远程对象交互的抽象协议,但是通常我们都不会直接实现它,而是继承自 Binder。
IBinder 接口中关键 API 就是 transact(),与之相对的是 Binder.onTransact() 方法。其中,transact 方法将允许你发送一个调用到 IBinder 对象;Binder.onTransact()方法用以接收并处理调用。transact方法是一个同步方法,它会一直等到onTransact处理完成返回之后,才会返回。
通过 transact() 发送的数据是 Parcel,本质上,Parcel是一般的缓冲区,持有内容以及内容元数据,而这些元数据被用来管理缓冲区中 IBinder 对象的引用。
这种机制确保了,当一个IBinder 被写入到Parcel并发送到另外一个进程中,然后又从另外一个进程中发回来时,原来的进程能收到同样的IBinder对象。这种语法,还允许IBinder、Binder对象使用一个唯一的id(可以作为token或者其他目的)以便进行跨进程管理。
每个运行的进程中,都有一个用于 transaction 的线程池。这些线程池被用来处理所有的来自于其它进程的IPC。举个例子,从进程A向进程B进行IPC,A中的线程阻塞在 transact()当它将事务发送到进程B 时。
B中的线程池接收到该事务,在目标对象上调用Binder.onTransact(),接着回复一个Parcel作为结果。收到结果之后,进程A继续执行。在效果上,就像是在同一个进程中创建一个线程执行。

Binder

根据官方文档[译]:

实现了IBinder接口,是轻量级跨进程访问机制的核心。对于很多开发者而言,不需要直接使用这个类,而是描述好希望的接口,然后由 aidl 工具生成合适的 Binder 子类。

关于Binder的进一步解释,可以参考我的另外一篇文章图说Android Binder机制

AIDL使用


关于aidl在 android studio 中操作方式,网上教程有很多很多,这里就不再描述具体该怎么在项目中使用了,而是直接上代码,代码中有注释,描述了关键步骤。

public interface IRemoteService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    //在服务端将实现该类,并在onTransact中处理客户端发送过来的实体
    public static abstract class Stub extends android.os.Binder implements com.ugym.server.IRemoteService {
        private static final java.lang.String DESCRIPTOR = "com.test.server.IRemoteService";

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

        /**
         * Cast an IBinder object into an com.ugym.server.IRemoteService interface,
         * generating a proxy if needed.
         */
        public static com.test.server.IRemoteService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.test.server.IRemoteService))) {
                return ((com.test.server.IRemoteService) iin);
            }
            //在客户端将使用的是代理类,即这个Proxy
            return new com.test.server.IRemoteService.Stub.Proxy(obj);
        }

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

        // 服务端会在 onTransact 中处理来自客户端的Parcel
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            //根据 code 判断,客户端是想调用哪个方法
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_basicTypes: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    //获取调用参数
                    _arg0 = data.readInt();
                    long _arg1;
                    //获取调用参数
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_addUser: {
                    data.enforceInterface(DESCRIPTOR);
                    com.test.server.User _arg0;
                    if ((0 != data.readInt())) {
                       //获取调用参数
                        _arg0 = com.test.server.User.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    //该addUser方法就是在服务端自己实现的
                    this.addUser(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.test.server.IRemoteService {
            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;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void addUser(com.test.server.User user) throws android.os.RemoteException {
                //获取Parcel对象,调用参数
                android.os.Parcel _data = android.os.Parcel.obtain();
                //获取Parcel对象,返回结果
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((user != null)) {
                        _data.writeInt(1);
                        user.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    //客户端通过 transact 进行远程调用
                    mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        //为每个函数进行编号
        static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        //addUser 函数的编号
        static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
// in 表示传入数据, out 表示传出数据, inout 表示双向传递。注意含有 out 时 User 类需要实现 readFromParcel() 方法

    public void addUser(com.test.server.User user) throws android.os.RemoteException;
}

在服务端做代码实现,进行业务逻辑处理:

public class RemoteService extends Service {


    private IBinder mIBinder = new IRemoteService.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public void addUser(User user) throws RemoteException {
            Log.d("RemoteService","addUser") ;
        }
    };

    public RemoteService() {
    }

    @Override
    public IBinder onBind(Intent intent) {

        try {
            mIBinder.linkToDeath(new IBinder.DeathRecipient() {
                @Override
                public void binderDied() {

                }
            },0);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return mIBinder ;
    }
}

当进行bindService后,所有映射关系将被建立好。最终,客户端将得到服务端Binder的句柄,以此,通过Binder驱动和服务端通信。

写在最后


最后,还是那句话,AIDL 说白了,从上层看,就是一个通信协议。协议,就是规矩,就是约定,就是结构,就是架构。亦如 Http, 亦如 TCP,亦如IP。具体体现形式并不重要,重要的是 思路, 是结构化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容