美文网首页android跨进程通信我爱编程
安卓进程通信之Binder学习指南(二)

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

作者: 猿万 | 来源:发表于2018-04-11 10:12 被阅读0次

    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系统相关知识;自己看源码。

    相关文章

      网友评论

        本文标题:安卓进程通信之Binder学习指南(二)

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