安卓进程通信之Binder学习指南(二)

Binder到底是什么?

我们经常提到Binder,那么Binder到底是什么呢?

Binder的设计采用了面向对象的思想,在Binder通信模型的四个角色里面;他们的代表都是“Binder”,这样,对于Binder通信的使用者而言,Server里面的Binder和Client里面的Binder没有什么不同,一个Binder对象就代表了所有,它不用关心实现的细节,甚至不用关心驱动以及SM的存在;这就是抽象。

通常意义下,Binder指的是一种通信机制;我们说AIDL使用Binder进行通信,指的就是Binder这种IPC机制

对于Server进程来说,Binder指的是Binder本地对象

对于Client来说,Binder指的是Binder代理对象,它只是Binder本地对象的一个远程代理;对这个Binder代理对象的操作,会通过驱动最终转发到Binder本地对象上去完成;对于一个拥有Binder对象的使用者而言,它无须关心这是一个Binder代理对象还是Binder本地对象;对于代理对象的操作和对本地对象的操作对它来说没有区别。

对于传输过程而言,Binder是可以进行跨进程传递的对象;Binder驱动会对具有跨进程传递能力的对象做特殊处理:自动完成代理对象和本地对象的转换。

面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体(本地对象)位于一个进程中,而它的引用(代理对象)却遍布于系统的各个进程之中。最诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给其它进程,让大家都能访问同一Server,就象将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder在英文里的原意。

驱动里面的Binder

我们现在知道,Server进程里面的Binder对象指的是Binder本地对象,Client里面的对象值得是Binder代理对象;在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换;因此Binder驱动必然保存了每一个跨越进程的Binder对象的相关信息;在驱动中,Binder本地对象的代表是一个叫做binder_node的数据结构,Binder代理对象是用binder_ref代表的;有的地方把Binder本地对象直接称作Binder实体,把Binder代理对象直接称作Binder引用(句柄),其实指的是Binder对象在驱动里面的表现形式;读者明白意思即可。

OK,现在大致了解Binder的通信模型,也了解了Binder这个对象在通信过程中各个组件里面到底表示的是什么。

深入理解Java层的Binder

IBinder/IInterface/Binder/BinderProxy/Stub

我们使用AIDL接口的时候,经常会接触到这些类,那么这每个类代表的是什么呢?

IBinder是一个接口,它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递;这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。

IBinder负责数据传输,那么client与server端的调用契约(这里不用接口避免混淆)呢?这里的IInterface代表的就是远程server对象具有什么能力。具体来说,就是aidl里面的接口。

Java层的Binder类,代表的其实就是Binder本地对象。BinderProxy类是Binder类的一个内部类,它代表远程进程的Binder对象的本地代理;这两个类都继承自IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。

在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要我们手动完成,这里使用了策略模式。

AIDL过程分析

现在我们通过一个AIDL的使用,分析一下整个通信过程中,各个角色到底做了什么,AIDL到底是如何完成通信的。(如果你连AIDL都不熟悉,请先查阅官方文档)

首先定一个一个简单的aidl接口:


// ICompute.aidl

package com.example.test.app;

interface  ICompute{

    int  add(int  a  ,  int  b);

}

然后用编译工具编译之后,可以得到对应的ICompute.java类,看看系统给我们生成的代码:

package com.example.test.app;

public interface ICompute extends android.os.IInterface{

    /**

    * Local-side IPC implementation stub class.

    */

    public static abstract class Stub extends android.os.Binder implements com.example.test.app.ICompute{

        private static final java.lang.String DESCRIPTOR ="com.example.test.app.ICompute";

        /**

        * Construct the stub at attach it to the interface.

        */

        publicStub(){

            this.attachInterface(this, DESCRIPTOR);

         }

        /**
        * Cast an IBinder object into an com.example.test.app.ICompute interface,
        * generating a proxy if needed.
        */

        public static com.example.test.app.ICompute as Interface(android.os.IBinder obj){

            if((obj ==null)) {

                return  null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

            if(((iin !=null) && (iin  instanceof  com.example.test.app.ICompute))) {

                return((com.example.test.app.ICompute) iin);

            }

            return  new  com.example.test.app.ICompute.Stub.Proxy(obj);

        }

        @Override

        public android.os.IBinder as Binder(){

            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_add: {

                    data.enforceInterface(DESCRIPTOR);

                    int _arg0;

                    _arg0 = data.readInt();

                    int  _arg1;
            
                    _arg1 = data.readInt();

                    int  _result =this.add(_arg0, _arg1);

                    reply.writeNoException();

                    reply.writeInt(_result);

                    return  true;

              }

           }

           return super.onTransact(code, data, reply, flags);

        }

        private static class Proxy implements com.example.test.app.ICompute{

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

            @Override

            public android.os.IBinder as Binder(){

                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 int add(int a,int b) throws android.os.RemoteException {

                android.os.Parcel  _data = android.os.Parcel.obtain();

                android.os.Parcel _reply = android.os.Parcel.obtain();

                int _result;

                try{

                    _data.writeInterfaceToken(DESCRIPTOR);

                    _data.writeInt(a);

                    _data.writeInt(b);

                     mRemote.transact(Stub.TRANSACTION_add, _data, _reply,0);

                    _reply.readException();

                    _result = _reply.readInt();

                }finally{

                    _reply.recycle();

                    _data.recycle();

                }

                return _result;

            }

    }

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

}

  /**

  * Demonstrates some basic types that you can use as parameters

  * and return values in AIDL.

  */

  public  int  add  (int  a,int  b)  throws  android.os.RemoteException;
}

系统帮我们生成了这个文件之后,我们只需要继承ICompute.Stub这个抽象类,实现它的方法,然后在Service 的onBind方法里面返回就实现了AIDL。这个Stub类非常重要,具体看看它做了什么。

Stub类继承自Binder,意味着这个Stub其实自己是一个Binder本地对象,然后实现了ICompute接口,ICompute本身是一个IInterface,因此他携带某种客户端需要的能力(这里是方法add)。此类有一个内部类Proxy,也就是Binder代理对象;

然后看看asInterface方法,我们在bind一个Service之后,在onServiceConnecttion的回调里面,就是通过这个方法拿到一个远程的service的,这个方法做了什么呢?


/**

* Cast an IBinder object into an com.example.test.app.ICompute interface,

* generating a proxy if needed.

*/

public static com.example.test.app.ICompute as Interface(android.os.IBinder obj){

    if((obj ==null)) {

        return  null;

     }

    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

    if(((iin !=null) && (iin instanceof com.example.test.app.ICompute))) {

        return((com.example.test.app.ICompute) iin);

    }

    return new com.example.test.app.ICompute.Stub.Proxy(obj);

}

首先看函数的参数IBinder类型的obj,这个对象是驱动给我们的,如果是Binder本地对象,那么它就是Binder类型,如果是Binder代理对象,那就是BinderProxy类型;然后,正如上面自动生成的文档所说,它会试着查找Binder本地对象,如果找到,说明Client和Server都在同一个进程,这个参数直接就是本地对象,直接强制类型转换然后返回,如果找不到,说明是远程对象(处于另外一个进程)那么就需要创建一个Binde代理对象,让这个Binder代理实现对于远程对象的访问。一般来说,如果是与一个远程Service对象进行通信,那么这里返回的一定是一个Binder代理对象,这个IBinder参数的实际上是BinderProxy;

再看看我们对于aidl的add 方法的实现;在Stub类里面,add是一个抽象方法,我们需要继承这个类并实现它;如果Client和Server在同一个进程,那么直接就是调用这个方法;那么,如果是远程调用,这中间发生了什么呢?Client是如何调用到Server的方法的?

我们知道,对于远程方法的调用,是通过Binder代理完成的,在这个例子里面就是Proxy类;Proxy对于add方法的实现如下:


@Override

public int add( int a, int b ) throws android.os.RemoteException {

    android.os.Parcel  _data = android.os.Parcel.obtain();

    android.os.Parcel  _reply = android.os.Parcel.obtain();

    int _result;

    try{

        _data.writeInterfaceToken(DESCRIPTOR);

        _data.writeInt(a);

        _data.writeInt(b);

        mRemote.transact(Stub.TRANSACTION_add, _data, _reply,0);

        _reply.readException();

        _result = _reply.readInt();

    }finally{

        _reply.recycle();

        _data.recycle();

    }

    return _result;

}

它首先用Parcel把数据序列化了,然后调用了transact方法;这个transact到底做了什么呢?这个Proxy类在asInterface方法里面被创建,前面提到过,如果是Binder代理那么说明驱动返回的IBinder实际是BinderProxy, 因此我们的Proxy类里面的mRemote实际类型应该是BinderProxy;我们看看BinderProxy的transact方法:(Binder.java的内部类)

public native boolean transact (int code, Parcel data, Parcel reply, int flags ) throws RemoteException ;

这是一个本地方法;它的实现在native层,具体来说在frameworks/base/core/jni/android_util_Binder.cpp文件,里面进行了一系列的函数调用,调用链实在太长这里就不给出了;要知道的是它最终调用到了talkWithDriver函数;看这个函数的名字就知道,通信过程要交给驱动完成了;这个函数最后通过ioctl系统调用,Client进程陷入内核态,Client调用add方法的线程挂起等待返回;驱动完成一系列的操作之后唤醒Server进程,调用了Server进程本地对象的onTransact函数(实际上由Server端线程池完成)。我们再看Binder本地对象的onTransact方法(这里就是Stub类里面的此方法):


@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_add: {

            data.enforceInterface(DESCRIPTOR);

            int_arg0;

            _arg0 = data.readInt();

            int _arg1;

             _arg1 = data.readInt();

            int _result =this.add(_arg0, _arg1);

            reply.writeNoException();

            reply.writeInt(_result);

            return true;

        }

    }

    return super.onTransact(code, data, reply, flags);

}

在Server进程里面,onTransact根据调用号(每个AIDL函数都有一个编号,在跨进程的时候,不会传递函数,而是传递编号指明调用哪个函数)调用相关函数;在这个例子里面,调用了Binder本地对象的add方法;这个方法将结果返回给驱动,驱动唤醒挂起的Client进程里面的线程并将结果返回。于是一次跨进程调用就完成了。

至此,你应该对AIDL这种通信方式里面的各个类以及各个角色有了一定的了解;它总是那么一种固定的模式:一个需要跨进程传递的对象一定继承自IBinder,如果是Binder本地对象,那么一定继承Binder实现IInterface,如果是代理对象,那么就实现了IInterface并持有了IBinder引用;

Proxy与Stub不一样,虽然他们都既是Binder又是IInterface,不同的是Stub采用的是继承(is 关系),Proxy采用的是组合(has 关系)。他们均实现了所有的IInterface函数,不同的是,Stub又使用策略模式调用的是虚函数(待子类实现),而Proxy则使用组合模式。为什么Stub采用继承而Proxy采用组合?事实上,Stub本身is一个IBinder(Binder),它本身就是一个能跨越进程边界传输的对象,所以它得继承IBinder实现transact这个函数从而得到跨越进程的能力(这个能力由驱动赋予)。Proxy类使用组合,是因为他不关心自己是什么,它也不需要跨越进程传输,它只需要拥有这个能力即可,要拥有这个能力,只需要保留一个对IBinder的引用。如果把这个过程做一个类比,在封建社会,Stub好比皇帝,可以号令天下,他生而具有这个权利(不要说宣扬封建迷信。。)如果一个人也想号令天下,可以,“挟天子以令诸侯”。为什么不自己去当皇帝,其一,一般情况没必要,当了皇帝其实限制也蛮多的是不是?我现在既能掌管天下,又能不受约束(Java单继承);其二,名不正言不顺啊,我本来特么就不是(Binder),你非要我是说不过去,搞不好还会造反。最后呢,如果想当皇帝也可以,那就是asBinder了。在Stub类里面,asBinder返回this,在Proxy里面返回的是持有的组合类IBinder的引用。

再去翻阅系统的ActivityManagerServer的源码,就知道哪一个类是什么角色了:IActivityManager是一个IInterface,它代表远程Service具有什么能力,ActivityManagerNative指的是Binder本地对象(类似AIDL工具生成的Stub类),这个类是抽象类,它的实现是ActivityManagerService;因此对于AMS的最终操作都会进入ActivityManagerService这个真正实现;同时如果仔细观察,ActivityManagerNative.java里面有一个非公开类ActivityManagerProxy, 它代表的就是Binder代理对象;是不是跟AIDL模型一模一样呢?那么ActivityManager是什么?他不过是一个管理类而已,可以看到真正的操作都是转发给ActivityManagerNative进而交给他的实现ActivityManagerService 完成的。

OK,本文就讲到这里了,要深入理解Binder,需要自己下功夫;那些native层以及驱动里面的调用过程,用文章写出来根本没有意义,需要自己去跟踪;接下来你可以:

看Android文档,Parcel, IBinder, Binder等涉及到跨进程通信的类;

不依赖AIDL工具,手写远程Service完成跨进程通信

《Binder设计与实现》

看老罗的博客或者书(书结构更清晰)

再看《Binder设计与实现》

学习Linux系统相关知识;自己看源码。

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

推荐阅读更多精彩内容