美文网首页Android源码解析Android进阶Android开发
Android源码解析RPC系列(一)---Binder原理

Android源码解析RPC系列(一)---Binder原理

作者: LooperJing | 来源:发表于2017-01-01 21:53 被阅读1384次

    转载请注明文章出处LooperJing

    看了几天的Binder,决定有必要写一篇博客,记录一下学习成果,Binder是Android中比较综合的一块知识了,目前的理解只限于JAVA层。首先Binder是干嘛用的?不用说,跨进程通信全靠它,操作系统的不同进程之间,数据不共享,对于每个进程来说,它都天真地以为自己独享了整个系统,完全不知道其他进程的存在,进程之间需要通信需要某种系统机制才能完成,在Android整个系统架构中,采用了大量的C/S架构的思想,所以Binder的作用就显得非常重要了,但是这种机制为什么是Binder呢?在Linux中的RPC方式有管道,消息队列,共享内存等,消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,这样就有两次拷贝过程。共享内存不需要拷贝,但控制复杂,难以使用。Binder是个折中的方案,只需要拷贝一次就行了。其次Binder的安全性比较好,好在哪里,在下还不是很清楚,基于安全性和传输的效率考虑,选择了Binder。Binder的英文意思是粘结剂,Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,这个进程一般是Server端,该对象提供了一套方法用以实现对服务的请求,而它的引用却遍布于系统的各个进程(Client端)之中,这样Client通过Binder的引用访问Server,所以说,Binder就像胶水一样,把系统各个进程粘结在一起了,废话确实有点多。

    一、概念理解

    为了从而保障了系统的安全和稳定,整个系统被划分成内核空间和用户空间
    内核空间:独立于普通的应用程序,可以访问受保护的内存空间,有访问底层硬件设备的所有权限。
    用户空间:相对与内核空间,上层运用程序所运行的空间就是用户空间,用户空间访问内核空间的唯一方式就是系统调用。一个4G的虚拟地址空间,其中3G是用户空间,剩余的1G是内核空间。如果一个用户空间想与另外一个用户空间进行通信,就需要内核模块支持,这个运行在内核空间的,负责各个用户进程通过Binder通信的内核模块叫做Binder驱动,虽然叫做Binder驱动,但是和硬件并没有什么关系,只是实现方式和设备驱动程序是一样的,提供了一些标准文件操作。

    Paste_Image.png

    二、Binder的通信模型

    在写AIDL的时候,一般情况下,我们有两个进程,一个作为Server端提供某种服务,然后另外一个进程作为Client端,连接Server端之后,就 可以使用Server里面定义的服务。这种思想是一种典型的C/S的思想。值得注意的是Android系统中的Binder自身也是C/S的架构,也有Server端与Client端。一个大的C/S架构中,也有一个小的C/S架构。

    先笼统的说一下,在整个Binder框架中,由系列组件组成,分别是Client、Server、ServiceManager和Binder驱动程序,其中Client、Server和ServiceManager运行在用户空间,Binder驱动程序运行内核空间。运行在用户空间中的Client、Server和ServiceManager,是在三个不同进程中的,Server进程中中定义了服务提供给Client进程使用,并且Server中有一个Binder实体,但是Server中定义的服务并不能直接被Client使用,它需要向ServiceManager注册,然后Client要用服务的时候,直接向ServiceManager要,ServiceManager返回一个Binder的替身(引用)给Client,这样Client就可以调用Server中的服务了。

    场景:进程A要调用进程B里面的一个draw方法处理图片。

    分析:在这种场景下,进程A作为Client端,进程B做为Server端,但是A/B不在同一个进程中,怎么来调用B进程的draw方法呢,首先进程B作为Server端创建了Binder实体,为其取一个字符形式,可读易记的名字,并将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,也就是向ServiceManager注册的过程,告诉ServiceManager,我是进程B,拥有图像处理的功能,ServiceManager从数据包中取出名字和引用以一个注册表的形式保留了Server进程的注册信息。为什么是以数据包的形式呢,因为这是两个进程,直接传递对象是不行滴,只能是一些描述信息。现在Client端进程A联系ServiceManager,说现在我需要进程B中图像处理的功能,ServiceManager从注册表中查到了这个Binder实体,但是呢,它并不是直接把这个Binder实体直接给Client,而是给了一个Binder实体的代理,或者说是引用,Client通过Binder的引用访问Server。分析到现在,有个关键的问题需要说一下,ServiceManager是一个进程,Server是另一个进程,Server向ServiceManager注册Binder必然会涉及进程间通信。当前实现的是进程间通信却又要用到进程间通信,这就好象蛋可以孵出鸡前提却是要找只鸡来孵蛋,确实是这样的,ServiceManager中预先有了一个自己的Binder对象(实体),就是那只鸡,然后Server有个Binder对象的引用,就是那个蛋,Server需要通过这个Binder的引用来实现Binder的注册。鸡就一只,蛋有很多,ServiceManager进程的Binder对象(实体)仅有一个,其他进程所拥有的全部都是它的代理。同样一个Server端Binder实体也应该只有一个,对应所有Client端全部都是它的代理。

    我们再次理解一下Binder是什么?在Binder通信模型的四个角色里面;他们的代表都是“Binder”,一个Binder对象就代表了所有,包括了Server,Client,ServiceManager,这样,对于Binder通信的使用者而言,不用关心实现的细节。对Server来说,Binder指的是Binder实体,或者说是本地对象,对于Client来说,Binder指的是Binder代理对象,也就是Binder的引用。对于Binder驱动而言,在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换。

    简单的总结一下,通过上面一大段的分析,一个Server在使用的时候需要经历三个阶段

    • 服务注册 Server端与SystemManager之间的IPC
    • 服务检索 Client端与SystemManager之间的IPC
    • 服务使用 Client端与Server端之间的IPC

    三、AIDL过程分析

    1、定义一个AIDL文件
    Game.aidl

    package test.wangjing.com.aidl.domain;
    parcelable Game;
    

    GameManager .aidl

    package test.wangjing.com.aidl;
    
    import test.wangjing.com.aidl.domain.Game;
    
    interface GameManager {
              Game querryGameById(int pGameId);
    }
    

    2、定义远端服务Service
    在远程服务中的onBind方法,实现AIDL接口的具体方法,并且返回Binder对象

        @Override
        public IBinder onBind(Intent intent) {
            return mGameManager;
        }
    
        GameManager.Stub mGameManager = new GameManager.Stub() {
            @Override
            public IBinder asBinder() {
                return null;
            }
    
            @Override
            public Game querryGameById(int pGameId) throws RemoteException {
                return mGameMap.get(pGameId);
            }
        };
    

    3、本地创建连接对象

     private class GameServiceConnection implements ServiceConnection {
    
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    
                mGameManager = (GameManager.Stub) GameManager.Stub.asInterface(iBinder);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
    
            }
        }
    
    public void actionService(View view) {
            Intent intent = new Intent(this, GameService.class);
            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        }
    

    以上就是一个远端服务的一般套路,如果是在两个进程中,就可以进程通信了,现在我们分析一下,这个通信的流程。重点是GameManager这个编译生成的类。

    public interface GameManager extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements test.wangjing.com.aidl.GameManager {
            private static final java.lang.String DESCRIPTOR = "test.wangjing.com.aidl.GameManager";
    
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an test.wangjing.com.aidl.GameManager interface,
             * generating a proxy if needed.
             */
            public static test.wangjing.com.aidl.GameManager asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof test.wangjing.com.aidl.GameManager))) {
                    return ((test.wangjing.com.aidl.GameManager) iin);
                }
                return new test.wangjing.com.aidl.GameManager.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_querryGameById: {
                        data.enforceInterface(DESCRIPTOR);
                        int _arg0;
                        _arg0 = data.readInt();
                        test.wangjing.com.aidl.domain.Game _result = this.querryGameById(_arg0);
                        reply.writeNoException();
                        if ((_result != null)) {
                            reply.writeInt(1);
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                        } else {
                            reply.writeInt(0);
                        }
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
    
            private static class Proxy implements test.wangjing.com.aidl.GameManager {
                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 test.wangjing.com.aidl.domain.Game querryGameById(int pGameId) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    test.wangjing.com.aidl.domain.Game _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeInt(pGameId);
                        mRemote.transact(Stub.TRANSACTION_querryGameById, _data, _reply, 0);
                        _reply.readException();
                        if ((0 != _reply.readInt())) {
                            _result = test.wangjing.com.aidl.domain.Game.CREATOR.createFromParcel(_reply);
                        } else {
                            _result = null;
                        }
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
    
            static final int TRANSACTION_querryGameById = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        }
    
        public test.wangjing.com.aidl.domain.Game querryGameById(int pGameId) throws android.os.RemoteException;
    }
    

    从类的关系来看,首先接口GameManager 继承 IInterface ,IInterface是一个接口,在GameManager内部有一个内部类Stub,Stub继承了Binder,(Binder实现了IBinder),并且实现了GameManager接口,在Stub中还有一个内部类Proxy,Proxy也实现了GameManager接口,一个整体的结构是这样的

    现在的问题是,Stub是什么?Proxy又是什么?在上面说了在Binder通信模型的四个角色里面;他们的代表都是“Binder”,一个Binder对象就代表了所有,包括了Server,Clinet,ServiceManager,为了两个进程的通信,系统给予的内核支持是Binder,在抽象一点的说,Binder是系统开辟的一块内存空间,两个进程往这块空间里面读写数据就行了,Stub从Binder中读数据,Proxy向Binder中写数据,达到进程间通信的目的。首先我们分析Stub。

    public static abstract class Stub extends android.os.Binder implements test.wangjing.com.aidl.GameManager
    

    Stub 类继承了Binder ,说明了Stub有了跨进程传输的能力,实现了GameManager接口,说明它有了根据游戏ID查询一个游戏的能力。我们在bind一个Service之后,在onServiceConnecttion的回调里面,就是通过asInterface方法拿到一个远程的service的。

     public static test.wangjing.com.aidl.GameManager asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof test.wangjing.com.aidl.GameManager))) {
                    return ((test.wangjing.com.aidl.GameManager) iin);
                }
                return new test.wangjing.com.aidl.GameManager.Stub.Proxy(obj);
            }
    
    

    asInterface调用queryLocalInterface。

     public IInterface queryLocalInterface(String descriptor) {
            if (mDescriptor.equals(descriptor)) {
                return mOwner;
            }
            return null;
        }
    

    mDescriptor,mOwner其实是Binder的成员变量,Stub继承了Binder,在构造函数的时候,对着两个变量赋的值。

     public Stub() {
          this.attachInterface(this, DESCRIPTOR);
       }
    
    public void attachInterface(IInterface owner, String descriptor) {
            mOwner = owner;
            mDescriptor = descriptor;
        }
    

    如果客户端和服务端是在一个进程中,那么其实queryLocalInterface获取的就是Stub对象,如果不在一个进程queryLocalInterface查询的对象肯定为null,因为不同进程有不同虚拟机,肯定查不到mOwner对象的,所以这时候其实是返回的Proxy对象了。拿到Stub对象后,通常在onServiceConnected中,就把这个对象转换成我们多定义AIDL接口。

    private class GameServiceConnection implements ServiceConnection {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                mGameManager = (GameManager.Stub) GameManager.Stub.asInterface(iBinder);
            }
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
    
            }
        }
    

    比如我们这里会转换成GameManager,有了GameManager对象,就可以调用后querryGameById方法了。如果是一个进程,那直接调用的是自己的querryGameById方法,如果不是一个进程,那调用了就是代理的querryGameById方法了。

    private static class Proxy implements test.wangjing.com.aidl.GameManager {
                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 test.wangjing.com.aidl.domain.Game querryGameById(int pGameId) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    test.wangjing.com.aidl.domain.Game _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeInt(pGameId);
                        mRemote.transact(Stub.TRANSACTION_querryGameById, _data, _reply, 0);
                        _reply.readException();
                        if ((0 != _reply.readInt())) {
                            _result = test.wangjing.com.aidl.domain.Game.CREATOR.createFromParcel(_reply);
                        } else {
                            _result = null;
                        }
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
    
    

    看到其中关键的一行是

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

    mRemote就是一个IBinder对象,相对于Stub,Proxy 是组合关系(HAS-A),内部有一个IBinder对象mRemote,Stub是继承关系(IS-A),直接实现了IBinder接口。

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

    transact是个native方法,最终还会回掉JAVA层的onTransact方法。

    @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_querryGameById: {
                        data.enforceInterface(DESCRIPTOR);
                        int _arg0;
                        _arg0 = data.readInt();
                        test.wangjing.com.aidl.domain.Game _result = this.querryGameById(_arg0);
                        reply.writeNoException();
                        if ((_result != null)) {
                            reply.writeInt(1);
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                        } else {
                            reply.writeInt(0);
                        }
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
    

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

    ***Please accept mybest wishes for your happiness and success ! ***

    相关文章

      网友评论

      • 1024a21fd2cb:纯应用层的解析哈,简洁、明朗,个人觉得需要配上流程图,这样会更加明晰,在客户端向服务端发送消息时候,客户端挂起,如果有配上整个流程图,相信会比文字更好理解。就是一点小建议哈~
      • 不二小姐的不二先生:如果能说下怎么从代理类proxy传递数据到实现类就好了😬
        LooperJing:@不二小姐的不二先生 这篇写的还是很浅显,如果想深入了解这个问题,得深入研究一下
      • 请叫我田胖子:哎 你的电量优化拉 楼主,你很久没更新简书咯。
        LooperJing:@请叫我田胖子 确实有一阵子没更新了,这阵子在工作交接,处理好继续更新,保证产量
      • 小草房Fang:写的很不错.
        LooperJing: @小草房Jason 😊

      本文标题:Android源码解析RPC系列(一)---Binder原理

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