移动架构07-Binder核心机制

作者: 最爱的火 | 来源:发表于2018-07-12 09:52 被阅读38次

    移动架构07-Binder核心机制

    一、什么是Binder

    1. 从机制上说,Binder是一种Android中实现跨进程通信(IPC)的方式;
    2. 从设备上说,Binder是一种虚拟的物理设备驱动;
    3. 从代码上说,BInder 是IBinder接口的实现类,将Binder机制模型以代码的形式 实现在整个Android系统中。

    二、为什么要使用Binder

    Android系统底层是Linux系统,Linux系统原本就具有IPC方式,但是Andriod为什么不使用Linux的IPC方式,而要设计Binder机制呢?

    Linux的IPC方式主要有8种:

    1. 管道 (PIPE),实际是用于进程间通信的一段共享内存。缺点:需要将一块内存拷贝两次,第一次拷贝到共享区,第二次拷贝到目标进程,效率低。
    2. 命名管道(FIFO),是一种特殊类型的文件。缺点:是基于文件的io操作,性能差。
    3. 信号 (signal),是一种以事件为驱动的通信方式。缺点:不适合信息的传递。
    4. 消息队列(Message queues),是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容。缺点:跟管道一样。
    5. 信号量(Semaphore),是一种计数器,用于控制对多个进程共享的资源进行的访问。缺点:跟信号一样。
    6. 共享内存(Share Memory),是在多个进程之间共享内存区域的一种进程间的通信方式。缺点:需要自己实现进程间的同步。
    7. 内存映射(Memory Map),是由一个文件到一块内存的映射。缺点:需要自己实现进程间的同步。
    8. 套接字 (Socket),是一种跨网络的通信方式。缺点:传输效率低,开销大。

    Binder相比传统的IPC方式,主要有两个优点:

    1. 效率高。Binder采用内存共享的机制,但是只要进行一次内存拷贝。
    2. 更安全。Linux的IPC方式无法获取目标进程的UID/PID,无法鉴别身份;Android可以获取目标进程的进程的UID/PID,从而控制访问权限。

    三、Binder的使用

    Binder依赖于Service,在组件(Activty)中通过bindService(),就可以获取Service中的Binder对象,实现Service与Activity的通信。

    服务分为本地服务和远程服务,都可以使用Binder。

    1.本地服务

    在本地服务中使用Binder,只需要两步:

    1. 声明一个Service,重写onBind(),返回一个继承Binder的自定义对象;
    2. 声明一个ServiceConnection,重写onServiceConnected(),获取IBinder对象,然后调用Activity的bindService();

    声明Service:

    public class Service2 extends Service {
        String TAG = "TestService";
        MyBinder myBinder = new MyBinder();
    
        @Override
        public IBinder onBind(Intent intent) {
            Log.d(TAG, "onBind() executed");
            return myBinder;
        }
    
        //定义一个类实现IBinder接口(Binder实现了IBinder接口)
        class MyBinder extends Binder {
            public void doSomething() {
                Log.d("TAG", "doSomething() executed");
            }
        }
    }
    

    在Activity中绑定服务:

    private void myBindService() {
        ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                myBinder = (Service2.MyBinder) service;
                myBinder.doSomething();
            }
            
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.d(TAG, "onServiceDisconnected() executed");
            }
        };
        Intent bindIntent = new Intent(this, Service2.class);
        //此时使用bindService开启服务
        bindService(bindIntent, connection, BIND_AUTO_CREATE);
        //销毁服务
        unbindService(connection);
    }
    

    2.远程服务

    在远程服务中使用Binder,需要三步:创建aidl、声明Service、调用bindService。

    创建aidl

    aidl是进程间通信接口,需要在客户端(声明Service的应用)和服务端(调用Service的应用)同时声明,并且要完全一致。

    首先,在服务端端创建aidl:选中项目目录->右键选中new->然后选中AIDL->填写文件名(比如:TestAidl)->修改aidl的代码。

    // TestAidl.aidl
    package gsw.demopluggable;
    
    // Declare any non-default types here with import statements
    interface TestAidl {
        String getName();
    
        String setName(String name);
    }
    

    然后,在客户端创建aidl,步骤同上。客户端的aidl要与服务端的一模一样,包括包名、类名、方法。

    然后,选中Build->Make Project,编译整个工程,就会在build/generated/source/aidl/debug目录下生产一个名为TestAidl的接口。

    声明Service

    在服务端,声明一个类(比如:MyStub)继承TestAidl.Stub(TestAidl的代理类,系统帮我们生成的),然后声明一个继承Service的类,在onBind()中返回MyStub对象。

    public class ServiceBinder extends Service {
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return new MyStub();
        }
    
        public static class MyStub extends TestAidl.Stub {
            String name = null;
    
            @Override
            public String getName() throws RemoteException {
                return name;
            }
    
            @Override
            public String setName(String name) throws RemoteException {
                this.name = name;
                return name;
            }
        }
    }
    

    调用bindService

    在客户端,声明一个匿名内部类ServiceConnection,重写onServiceConnected(),通过TestAidl.Stub.asInterface(iBinder)获取服务端的TestAidl对象。

    然后,通过远程服务的action调用bindService()。

    /**
     * 绑定远程服务
     * action为"gsw.demopluggable2.binder.ServiceBinder"
     */
    private void bindRemoteServer() {
        Intent intent = new Intent();
        intent.setAction("gsw.demopluggable2.binder.ServiceBinder");
        intent.setPackage("gsw.demopluggable2");
    
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                TestAidl testAidl = TestAidl.Stub.asInterface(iBinder);
    
                try {
                    testAidl.setName("David");
                    Toast.makeText(ActivityBinder.this, "--->  " + testAidl.getName(), Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
    
            }
        }, Context.BIND_AUTO_CREATE);
    }
    

    3.使用自定义类型

    aidl中,使用自定义类型,需要在创建一个对应的aidl文件来声明这个类型,并且这个类必须为Parcelable类型。

    首先,创建Java类,实现Parcelable接口。

    package gsw.demopluggable.binder;
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Item1 implements Parcelable {
        public String name;
        public int id;
        protected Item1(Parcel in) {
            name = in.readString();
            id = in.readInt();
        }
        public Item1(String name, int id) {
            this.name = name;
            this.id = id;
        }
        public static final Creator<Item1> CREATOR = new Creator<Item1>() {
            @Override
            public Item1 createFromParcel(Parcel in) {
                return new Item1(in);
            }
            @Override
            public Item1[] newArray(int size) {
                return new Item1[size];
            }
        };
        @Override
        public int describeContents() {
            return 0;
        }
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeInt(id);
        }
    }
    

    然后,创建一个对应的aidl文件:Item1.adil。它们两个的包名必须相同,且必须服务端的包名相同。

    // Item1.aidl
    package gsw.demopluggable.binder;
    //声明是Parcelable类型,作为参数使用
    parcelable Item1;
    

    然后,就可以在其它的aidl中引用Item1.adil。

    // TestAidl.aidl
    package gsw.demopluggable;
    //import自定义类型
    import gsw.demopluggable.binder.Item1;
    import gsw.demopluggable.binder.Item2;
    
    // Declare any non-default types here with import statements
    interface TestAidl {
        String getName();
    
        String setName(String name);
    
        //Item1作为参数时,需要在前面加in,代表输入类型
        Item2 getItem2(in Item1 item1);
    }
    

    四、Binder的原理

    1.内核共享

    Binder是通过共享内存的方式,实现进程间通信的。

    那么,共享的是哪一块内存呢?我们看下面这张图:

    Binder机制-001.png

    每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。

    Client进程向Server进程通信,就是获取Server进程的Binder对象,只需要从Server进程的用户空间拷贝一份内存到Client进程的用户空间。

    2.通信流程

    通信流程可以分为3步:注册服务、获取服务、使用服务。

    Binder机制-002.png

    首先需要注册服务,服务端通过 ServiceManager 注册服务。注册的过程就是向 Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。

    然后是获取服务端。获取服务端的方式就是通过 ServiceManager 向 svcinfo 列表中查询一下返回服务端的代理,svcinfo 列表就是所有已注册服务的通讯录,保存了所有注册的服务信息。

    最后是使用服务。使用服务就是通过Binder驱动将服务端的Binder对象,拷贝到客户端,然后通过Binder对象进行通讯。

    注意:客户端、服务端、ServiceManager都在不同的进程,它们之间都是通过Binder机制进行通讯的。

    3.aidl机制

    上面的通信流程好像跟aidl没什么关系,但是实际使用时却不能少,这是为什么呢?

    aidl是进程间通信接口,它不是Java的类,更像一种协议。它的作用是让AS自动生成Binder类,供客户端调用。

    aidl文件编译后,会生成一个Java接口,分为3层:TestAidl、Stub、Proxy。

    package gsw.demopluggable;
    
    public interface TestAidl extends android.os.IInterface {
        public static abstract class Stub extends android.os.Binder implements gsw.demopluggable.TestAidl {
            ...
        }
        private static class Proxy implements gsw.demopluggable.TestAidl {
           ...     
        }
        public java.lang.String getName() throws android.os.RemoteException;
    
        public java.lang.String setName(java.lang.String name) throws android.os.RemoteException;
    }
    

    TestAidl

    TestAidl就是aidl文件中声明的接口,也就是我们要给客户端调用的功能。

    Proxy

    Proxy,顾名思义,就是TestAidl的代理类,用来实现TestAidl的方法。Proxy的作用是将需要传递的参数转化为Parcel,从而跨进程传输。

    看下面的例子:

    public java.lang.String getName() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.lang.String _result;
        try {
            //用来标识_data是给含有DESCRIPTOR标志的Binder接口的参数
            _data.writeInterfaceToken(DESCRIPTOR);
            //调用Stub的transact,处理getName请求
            mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
            _reply.readException();
            //获取Stub的处理结果
            _result = _reply.readString();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
    

    Stub

    Stub是一个使用TestAidl装饰的Binder类,是我们实际传递给客户端的对象。通过Stub,客户端就可以调用TestAidl的方法。

    首先是DESCRIPTOR,默认值是包名+类名,作用是在IBinder中查找TestAidl接口。

    private static final java.lang.String DESCRIPTOR = "gsw.demopluggable.TestAidl";
    

    然后是asInterface(),作用是返回Proxy对象,也就是把TsetAidl对象返回给客户端。

    public static gsw.demopluggable.TestAidl asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof gsw.demopluggable.TestAidl))) {
            return ((gsw.demopluggable.TestAidl) iin);
        }
        return new gsw.demopluggable.TestAidl.Stub.Proxy(obj);
    }
    

    然后是onTransact(),作用是处理客户端的请求,并将处理结果返回给客户端。

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            //用来指定DESCRIPTOR,从而确定需要通讯的接口
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            //用来处理TestAidl的getName()
            case TRANSACTION_getName: {
                //说明data正准备给指定DESCRIPTOR的接口使用
                data.enforceInterface(DESCRIPTOR);
                java.lang.String _result = this.getName();
                //说明当前操作没有出现异常
                reply.writeNoException();
                reply.writeString(_result);
                return true;
            }
            //用来处理TestAidl的setName()
            case TRANSACTION_setName: {
                //说明data正准备给指定DESCRIPTOR的接口使用
                data.enforceInterface(DESCRIPTOR);
                java.lang.String _arg0;
                _arg0 = data.readString();
                java.lang.String _result = this.setName(_arg0);
                //说明当前操作没有出现异常
                reply.writeNoException();
                reply.writeString(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
    

    最后

    代码地址:https://gitee.com/yanhuo2008/Common/tree/master/DemoPluggable

    移动架构专题:https://www.jianshu.com/nb/25128604

    喜欢请点赞,谢谢!

    参考文章

    https://blog.csdn.net/a987073381/article/details/52006729

    https://blog.csdn.net/freekiteyu/article/details/70082302

    相关文章

      网友评论

        本文标题:移动架构07-Binder核心机制

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