Android Binder 全解析(3) -- AIDL原理剖析

IBinder 与 Binder

一个在同进程的对象的抽象是 Object,但这个对象是不能被跨进程使用的,要想跨进程使用,在 Android 中就必须依附于 Binder Framework。基于抽象设计的原理,Android系统将一个可远程操作的应用定义为 IBinder,在这个接口中定义了一个可远程调用对象应该具有的属性和方法,在代码中实际使用的Binder 也都是继承自 IBinder 对象。我们接下来分析 IBinder 中定义了那些接口,是怎么抽象出可远程调用相关的方法的。

public interface IBinder {

    // 查看 binder 对应的进程是否存活
    public boolean pingBinder ();

    // 查看 binder 是否存活,需要注意的是,可能在返回的过程中,binder 不可用
    public boolean isBinderAlive ();

    /**
     * 执行一个对象的方法,
     *
     * @param 需要执行的命令
     * @param 传输的命令数据,这里一定不能为空
     * @param 目标               Binder 返回的结果,可能为空
     * @param 操作方式,0           等待 RPC 返回结果,1 单向的命令,最常见的就是 Intent.
     */
    public boolean transact (int code, Parcel data, Parcel reply, int flags) throws RemoteException;

    // 注册对Binder死亡通知的观察者,在其死亡后,会收到相应的通知
    // 这里先跳过,后续
    public void linkToDeath (DeathRecipient recipient, int flags) throws RemoteException;
}

如注释中所述,最重要的方法是 transact 方法,要理解这个方法是干嘛的,得看ipcthreadstate.cpp
源代码里的说明。

status_t IPCThreadState::transact(int32_t handle,
        uint32_t code,const Parcel&data,
        Parcel*reply,uint32_t flags)
    {
        status_t err=data.errorCheck();
        flags|=TF_ACCEPT_FDS;

        ......

        if(err==NO_ERROR){
        LOG_ONEWAY(">>>> SEND from pid %d uid %d %s",getpid(),getuid(),
        (flags&TF_ONE_WAY)==0?"READ REPLY":"ONE WAY");
        err=writeTransactionData(BC_TRANSACTION,flags,handle,code,data,NULL);
        }

        if(err!=NO_ERROR){
        if(reply)reply->setError(err);
        return(mLastError=err);
        }

        if((flags&TF_ONE_WAY)==0){
        ......
        if(reply){
        err=waitForResponse(reply);
        }else{
        Parcel fakeReply;
        err=waitForResponse(&fakeReply);
        }
        ......
        }else{
        err=waitForResponse(NULL,NULL);
        }

        return err;
    }

这是截取过后的源码,刨除了一些支线逻辑,从这个代码里面可以看到的主要逻辑就是:根据Uid 和 Pid (用户id和进程id)进行相应的校验,校验通过后,将相应的数据写入 writeTransactionData ,其后在 waitForResponse 里面读取前面写入的值,并执行相应的方法,最后返回结果。

总结地来说,就是 IBinder 抽象了远程调用的接口,任何一个可远程调用的对象都应该实现这个接口。由于 IBinder 对象是一个高度抽象的结构,直接使用这个接口对于应用层的开发者而言学习成本太高,需要涉及到不少本地实现,因而 Android 实现了 Binder 作为 IBinder 的抽象类,提供了一些默认的本地实现,当开发者需要自定义实现的时候,只需要重写 Binder 中的protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 方法即可。


自动售货机的故事

先偏一个题,夏天一到,天气也变得炎热,地铁旁边的自动售货机开始有更多的人关顾了。那么售货机是怎么工作的了?通过对这个分析,可以对Binder Proxy/Stub 模式更好地理解。

和我们打交道得是售货机,而不是背后的零售商,道理很简单,零售商的位置是固定的,也就意味着有很大的交通成本,而和售货机打交道就轻松很多,毕竟售货机就在身边。因而从客户端的角度上看,只需要知道售货机即可。

再从零售商的角度来看,要和为数众多的售货机打交道也是不容易的事情,需要大量的维护和更新成本,如果将起交由另一个中介公司,就能够省心不少。零售商只关心这个中介公司,按时发货,检查营收,这就是所有它应该完成的工作。

如上图所示,在 Binder Framework 中也采用了类似的结构,Proxy 就相当于前面提及的售货机,Stub 相当于这里的中介公司,在这样的设计下,客户端只需要和 Proxy 打交道,服务端只需要和 Stub 打交道,调理清晰很多。这种模式又被称为 Proxy / Stub 模式,这种模式也值得我们在日后的开发中借鉴。另外需要注意的是,为了开发的需要,通常 Proxy 和 Stub 实现了相同的接口。

在这里 Stub 是具体的远程方法实现,也被称为存根,Proxy 继承了相应的接口,但只是在这里面调用远程方法,并返回相应的结果。


AIDL 是什么?它是如何运作的?

虽然上述的模式帮助我们将远程方法调用与服务具体实现解耦开来,但是这里面还是又不少代码需要实现,而且远程方法调用这一块应该是重复代码,那么有什么方法来帮我们简化这个步骤吗?

Android 的设计者在一开始也意识到这个问题,推出了 AIDL,如下图所示

现在我们来分析下,Android Framework 是如何把 AIDL 运行起来的,首先来看看一个大数相乘的例子,想把这个耗时的操作,放到另一个进程上来调用,于是定义了下面的 IMultiply.aidl 文件。

// IMultiply.aidl
package com.wandoujia.baymax.aidl;

interface IMultiply {
    long multiply(long left, long right);
}

在 Android Studio 中点击 Clean / Rebuild Project 之后,可以在 build / genreated / Source 的目录里面找到 IMultiply.java 文件,具体的代码如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/QisenTang/Program/Wandoulabs/baymax/packages/baymax/src/main/aidl/com/wandoujia/baymax/aidl/IMultiply.aidl
 */
package com.wandoujia.baymax.aidl;
// Declare any non-default types here with import statements

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

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

        /**
         * Cast an IBinder object into an com.wandoujia.baymax.aidl.IMultiply interface,
         * generating a proxy if needed.
         */
        public static com.wandoujia.baymax.aidl.IMultiply asInterface (android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.wandoujia.baymax.aidl.IMultiply))) {
                return ((com.wandoujia.baymax.aidl.IMultiply) iin);
            }
            return new com.wandoujia.baymax.aidl.IMultiply.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 {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString (DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_multiply: {
                    data.enforceInterface (DESCRIPTOR);
                    long _arg0;
                    _arg0 = data.readLong ();
                    long _arg1;
                    _arg1 = data.readLong ();
                    long _result = this.multiply (_arg0,
                                                  _arg1);
                    reply.writeNoException ();
                    reply.writeLong (_result);
                    return true;
                }
            }
            return super.onTransact (code,
                                     data,
                                     reply,
                                     flags);
        }

        private static class Proxy implements com.wandoujia.baymax.aidl.IMultiply {
            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 long multiply (long left, long right) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain ();
                android.os.Parcel _reply = android.os.Parcel.obtain ();
                long _result;
                try {
                    _data.writeInterfaceToken (DESCRIPTOR);
                    _data.writeLong (left);
                    _data.writeLong (right);
                    mRemote.transact (Stub.TRANSACTION_multiply,
                                      _data,
                                      _reply,
                                      0);
                    _reply.readException ();
                    _result = _reply.readLong ();
                } finally {
                    _reply.recycle ();
                    _data.recycle ();
                }
                return _result;
            }
        }

        static final int TRANSACTION_multiply = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public long multiply (long left, long right) throws android.os.RemoteException;
}

这段自动的生成的代码,可读性上不是很好,但不影响我们进行分析。这段自动生成的代码中,最重要的是三个部分,Proxy,Stub和asInterface。

首先看看Proxy的实现,首先是将远程的Binder作为参数传入进来,再来看看 multiply 这个方法里面的下面几个步骤。首先是将left 和 right的参数写入到_data中去,同时在 远程binder 调用结束后,得到返回的 _reply ,在没有异常的情况下,返回_reply.readLong()的结果。根据 Android Binder 完全解析(一)概述 提到的内容,这里可以粗略地将 Binder 看做远程服务抛出的句柄,通过这个句柄就可以执行相应的方法了。这里需要额外说明的是,写入和传输的数据,都是 Parcelable,Android Framework 中提供的。

_data.writeInterfaceToken(DESCRIPTOR);
_data.writeLong(left);
_data.writeLong(right);
mRemote.transact(Stub.TRANSACTION_multiply, _data, _reply, 0);
_reply.readException();
_result = _reply.readLong();

再来看看Stub里面的实现,Stub本身继承了Binder抽象类,本身将作为一个句柄,实现在服务端,但传递给客户端使用。同样也看看 onTransact里面的方法,首先是通过 long _arg0; _arg0 = data.readLong();读取 left 和 right 参数,在this.multiply(_arg0, _arg1)计算过后,将结果写入到reply中。

细心的读者,可以从上面的描述中,发现一些有意思的东西。Proxy 是写入参数,读取值;Stub 是读取参数,写入值。正好是一对,那因此我们是不是可以做出这样的论断呢?Proxy 和 Stub 操作的是一份数据?恭喜你,答对了。

在前文提及的内容里,用户空间和内核空间是互相隔离的,客户端和服务端在同一用户空间,而 Binder Driver 在内核空间中,常见的方式是通过copy_from_user 和 copy_to_user 两个系统调用来完成,但 Android Framework 考虑到这种方式涉及到两次内存拷贝,在嵌入式系统中不是很合适,于是 Binder Framework 通过 ioctl 系统调用,直接在内核态进行了相关的操作,节省了宝贵的空间。

可能大家也注意点 Proxy这里是private权限的,外部是无法访问的,但这里是 Android 有意为之,抛出了 asInterface方法,这样避免了对 Proxy可能的修改。


系统使用AIDL的场景

AIDL 被Android 系统广泛使用,在许多地方都能看到相应的场景,这里以 电话服务 作为例子,来简单说明下如何在系统没有提供挂断电话的API的情况下强行挂电话。

通过的 ITelephony.aidl 的查看,可以在里面找到相应的接口,不过这个类是 internal 包里面的,应用层无法访问。那怎么来完成这个操作了?

 /**
  * End call if there is a call in progress, otherwise does nothing.
  *
  * @return whether it hung up
  */
 boolean endCall();

首先在相同包名下申明同样的AIDL文件,再通过编译后,就会生成相应的 Stub 文件。其次通过反射拿到 TELEPHONY_SERVICE 的binder。这个 binder 就是可以操作另一个进程来挂断电话的句柄。将这个binder 作为 Proxy 的参数,并通过 asInterface 注入进去,从何获得相应的接口,最后调用 telephony.endCall() 即可完成操作。

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

推荐阅读更多精彩内容