Binder使用

作者: 许峻华 | 来源:发表于2018-02-24 17:14 被阅读531次

    Binder

    • 在安卓使用Binder实现进程间通信需要做哪些工作
    • 如何模糊跨进程调用与进程内调用?
    • 如何使用AIDL

    如何利用Binder实现进程间通信

    我们先看下Binder调用大致原理,这是Binder调用的标准调用过程,我们下面的代码将逐渐从不标准过程转成标准过程:


    Binder.png

    首先,我们 android studio 新建两个工程(两个 moudle 也可以,这里目的为创建两个运行在不同进程的app),一个Server,一个Client,而后,在Server中,我们新建一个java类Stub(类名无所谓),继承android.os.Binder,之后重写onTransact 方法,此处注意,onTransact方法的四个参数:

    • code:方法标识符,因为Client端对Server端的所有调用都会走到Server端的这个方法,所以理所应当Client端应该传递一个参数过来用以表示要调用哪个方法,注意这个int类型的标识必须介于 FIRST_CALL_TRANSACTION 和 LAST_CALL_TRANSACTION之间,所以我们给方法分配code的时候最好使用FIRST_CALL_TRANSACTION+n 这种方式
    • data :Client传递过来的序列化数据包,Parcel类型
    • reply: 如果Client端调用时需要返回值,Server通过这个对象将返回值传递回去,同样Parcel类型
    • flag 用来区分这个调用是普通调用还是单边调用,普通调用时,Client端线程会阻塞,直到从Server端接收到返回值(所以如果Client端是主线程调用,其调用的Server端不宜做耗时操作,这会让造成Client的ANR),若flag==IBinder.FLAG_ONEWAY,则这次调用是单边调用,Client在传出数据后会立即执行下一段代码,此时两端异步执行,单边调用时函数返回值必须为void (也就是异步调用必须舍弃返回值,要返回值就必须阻塞等待)

    有以上,Server端的功能就已经可以实现,但在两端通信时,为了两端Binder匹配,我们还需要在Server端做一次验证,用到data.enforceInterface(DESCRIPTOR)这个方法,DESCRIPTOR是Binder描述符,Binder Server和Client之间将通过这个描述符做验证,要想通过验证Binder通信的两端DESCRIPTOR必须相同,这也是为什么我们在使用AIDL帮助我们生成Binder代码的时候,必须把AIDL放在相同的包名下,因为SDK会根据包名为我们生成对应的DESCRIPTOR字符串,这里我们手写Binder,只需要保证两端相同就好了,包名字符串不是必须的

    下面为Server端完整代码

    public class Stub extends android.os.Binder {
        //用于标识调用的Binder
        private static final java.lang.String DESCRIPTOR = "MyBinder";
        //方法标识,这里我们准备两个方法,一个无参,一个有参
        private static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        private static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case TRANSACTION_method0: {
                    //Store or read an IBinder interface token
                    //验证Binder标识
                    data.enforceInterface(DESCRIPTOR);
                    //调用实现方法
                    this.method0();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_method1: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    //按写入的顺序读取数据
                    _arg0 = data.readInt();           
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.method1(_arg0, _arg1);
                    reply.writeNoException();
                    //向Client写回返回值
                    reply.writeInt(_result);          
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
        //Server端真正实现业务的两个方法
        public void method0() throws RemoteException {
            Log.e("Server", Process.myPid() + "  " + Process.myTid() + "  " + Process.myUid() + "  " + "method0");
        }
        public int method1(int a, int b) throws RemoteException {
            Log.e("Server", Process.myPid() + "  " + Process.myTid() + "  " + Process.myUid() + "  " + "method1" + "  " + a + " " + b);
            return a + b;
        }
    }
    

    应用间要实现Binder通信必须要用Service来完成,想象客户端要怎样才能知道服务端的Binder地址并向其写入数据,一种是客户端通过一个Binder地址总管查询,通过键名查找到对应的Binder服务,这种方式就是有名Binder,这个总管类就是ServiceManager,应用进程获取系统服务就是通过查询这个Binder总管实现的,比如应用进程启动进入java层后就会去查找AMS的客户端,就是通过ServiceManager来查找的,但作为应用进程,是不能向ServiceManager注册有名Binder的,所以我们的客户端也没法通过ServiceManager查询到对应的Binder服务端,但应用进程间依然是可以获取到对方的Binder服务端的,Binder并不一定要注册到ServiceManager才能被获取到,这种Binder的获取方式就是通过已经获取到的Binder传递Binder,也就是说如果有某个有名Binder服务它提供了传递Binder的方法,那么我们就可以通过这个Binder服务来传递我们的匿名Binder,正好,AMS作为一个有名Binder提供了这个功能,其对Binder传递被封装到了Service组件当中,我们可以通过Service.onBind 来返回我们要传递的匿名Binder客户端,而在Activity.bindService中获取到这个Binder:

        @Override
        public IBinder onBind(Intent intent) {
            return new Stub();
        }
    

    在Client端Activity 中bindService,我们来看Activity的代码:

        //定义常量
        //注意两个工程中对应的标识符必须相同
        static final String DESCRIPTOR = "MyBinder";
        //方法标识
        static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Intent intent = new Intent();
            intent.setComponent(new ComponentName("com.xxx.server", "com.xxx.server.ServerService"));
            boolean b = bindService(intent, conn, BIND_AUTO_CREATE);
            Log.e("Client", "      "+b);
        }
    

    bindService 的第二个参数,ServiceConnnection,在这个回调中我们取得IBinder对象,这个对象是Server端在Client中的一个代理对象

        ServiceConnection conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder iBinder) {
                //这个IBinder对象iBinder,可以调用c++层Binder代理并最终通过Binder驱动传递数据
                Parcel _data0 = Parcel.obtain();//申请传递参数的Parcel对象(从Parcel池中取出)
                Parcel _reply0 = Parcel.obtain();//申请接收返回值的Parcel对象,相当于数据载体
                Parcel _data1 = Parcel.obtain();
                Parcel _reply1 = Parcel.obtain();
                try {
                    //调用第一个方法
                    //写入Binder标识,以便服务端验证
                    _data0.writeInterfaceToken(DESCRIPTOR);
                    //传入方法标识,以便服务端知道我们要调用哪个方法,注意最后一个参数,就是上面提到的                  //flag,如果我们传入IBinder.FLAG_ONEWAY,则这次调用为单边调用,这个方法会立即返                //回,不会等服务端方法返回
                    iBinder.transact(TRANSACTION_method0, _data0, _reply0, 0);
                    _reply0.readException();
                    //调用第二个方法
                    _data1.writeInterfaceToken(DESCRIPTOR);
                    //按顺序写入参数
                    _data1.writeInt(1);
                    _data1.writeInt(2);
                    //从下面这行代码开始本线程会阻塞,直到服务端进程中调用的方法完成计算返回后这个线程继                 //续运行,计算的返回值放入_reply1中
                    iBinder.transact(TRANSACTION_method1, _data1, _reply1, 0);
                    _reply1.readException();
                    int i = _reply1.readInt();//从reply中读取返回值,这里我们就得到了服务端计算后的结果
                } catch (RemoteException e) {
                    e.printStackTrace();
                } finally {
                    //回收Parcel
                    _data0.recycle();
                    _reply0.recycle();
                    _data1.recycle();
                    _reply1.recycle();
                }
                Log.e("Client", Process.myPid() + "  " + Process.myTid() + "  " + Process.myUid() + "  " + "method0");
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    

    通过以上代码,我们就可以实现跨进程间的方法调用

    我们可以对onServiceConnected方法里的代码做一定封装,使用Proxy类封装对IBinder的操作,使得调用的时候更方便

    public class Proxy {
        static final String DESCRIPTOR = "MyBinder";
        static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        private android.os.IBinder mRemote;
        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }
    
        public void method0() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(TRANSACTION_method0, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    
        public int method1(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(TRANSACTION_method1, _data, _reply, 0);
                _reply.readException();
                _result = _reply.readInt();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    }
    

    此时ServiceConnection对象可以更改为

        ServiceConnection conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder iBinder) {
                Proxy proxy = new Proxy(iBinder);
                try {
                    proxy.method0();
                    int i = proxy.method1(1,2);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    

    如何模糊跨进程调用与进程内调用?

    分析问题

    • onServiceConnected(ComponentName name, IBinder iBinder) 这个方法传递的IBinder接口对象是什么,在同进程与不同进程时有什么不一样?
    • 如果通过bindService 传递过来的IBinder对象是同进程的,那我们还需要使用IBinder.transact传递数据吗?要知道的Binder的使用需要层层调用并最终在内核空间进行一次数据复制
    • 如果我们想跨进程的时候创建 Proxy 类包裹 IBinder 对象的操作,同进程的时候直接强转 IBinder 对象为我们定义的对象或接口,不通过代理类直接使用其方法,应该怎么做?

    对第一个问题,我们在 asInterface 里面打印一下传进来的IBinder实例是什么类型,发现如果是远程调用,传给我们的 iBinder 是 BinderProxy 类型,他在native层会对应一个C++的BpBinder,BpBinder 最终会通过Binder驱动跟Server端通信。如果是本地调用,打印出的类型为Stub,说明本地调用时,onServiceConnected传过来的就是我们在Service的onBinde方法返回的Stub对象本身。在这个基础上,为了让远程调用(通过我们新建Proxy封装BinderProxy对象)和本地调用(直接调用继承自Binder的Stub对象)统一,我们让Proxy和Stub实现相同的接口,再实现一个静态方法,根据传递的IBinder对象返回一个对象,这个对象实现我们定义的接口,在进程内调用时,这个对象就是Stub类及其子类对象,当跨进程调用时,这个对象就是Proxy实例,由于要考虑两种情况,我们就需要在这个静态方法中作出判断,判断传递的IBinder对象是本地对象还是远程对象,再根据判断决定直接强转为我们定义的接口返回,或生成Proxy对象强转返回。此时我们需要将两份文件合并。

    怎样区分IBinder对象的具体类型,我们可以通过IBinder的queryLocalInterface(DESCRIPTOR)方法,得到IInterface对象,判断是否为null:

        //定义静态方法根据传递的IBinder对象返回实现相同接口的不同对象
        public static IMyInterface asInterface(IBinder iBinder){
            if ((iBinder == null)) {
                return null;
            }
            IInterface iin = iBinder.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof IMyInterface))) {
                return ((IMyInterface)iin );
            }
            return new Proxy(iBinder);
        }
    

    若为Stub则返回值就是Stub实例本身,让我们看一下Binder.queryLocalInterface的实现

        /**
         * Use information supplied to attachInterface() to return the
         * associated IInterface if it matches the requested
         * descriptor.
         */
        public IInterface queryLocalInterface(String descriptor) {
            if (mDescriptor.equals(descriptor)) {
                return mOwner;
            }
            return null;
        }
    

    若为代理类BindProxy则为空,我们看下其实现:

        public IInterface queryLocalInterface(String descriptor) {
            return null;
        }
    

    将传进来的descriptor与mDescriptor比较,若相同,说明这是进程内调用,返回mOwner,这个mOwner和mDescriptor是需要Binder调用attachInterface赋值,所以我们在Stub构造方法里调用这个方法,又因为这个方法需要的参数为IInterface,所以我们让Stub实现这个接口,最后由于我们已经将Proxy和Stub文件合并,在我们需要给别的进程绑定我们的iBinder时,需要把这个文件添加到对应的应用里,我们这个就需要把Stub两个方法的实现抽离出来,具体的实现放在我们的业务代码里,让Server端Stub子类去实现

    public interface IMyInterface {
        //定义两个方法
        void method0() throws RemoteException;
        int method1(int a, int b) throws RemoteException;
    
        //用于标示调用的Binder
        static final java.lang.String DESCRIPTOR = "MyBinder";
    
        //用于标识调用的方法
        static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    
        //定义静态方法根据传递的IBinder对象返回实现相同接口的不同对象
        public static IMyInterface asInterface(IBinder iBinder){
            if ((iBinder == null)) {
                return null;
            }
            IInterface iin = iBinder.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof IMyInterface))) {
                return ((IMyInterface)iin );
            }
            return new Proxy(iBinder);
        }
    
        public abstract class Stub extends android.os.Binder implements IMyInterface,IInterface {
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            @Override
            public 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 TRANSACTION_method0: {
                        data.enforceInterface(DESCRIPTOR);//Store or read an IBinder interface token
                        this.method0();                   //
                        reply.writeNoException();
                        return true;
                    }
                    case TRANSACTION_method1: {
                        data.enforceInterface(DESCRIPTOR);
                        int _arg0;
                        _arg0 = data.readInt();           //按写入的顺序读取数据
                        int _arg1;
                        _arg1 = data.readInt();
                        int _result = this.method1(_arg0, _arg1);
                        reply.writeNoException();
                        reply.writeInt(_result);          //向Client写回返回值
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
        }
        public class Proxy implements IMyInterface {
            static final String DESCRIPTOR = "MyBinder";
            private android.os.IBinder mRemote;
    
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
    
            @Override
            public void method0() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(TRANSACTION_method0, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
    
            @Override
            public int method1(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(TRANSACTION_method1, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
    
        }
    
    }
    

    将如上文件放在需要通信的两个工程里,就可以实现Binder通信,如果同进程调用,就会直接使用返回的对象而不通过Binder机制。

    可以看出调用总是由客户端发起,由服务端运算结束。

    怎么实现进程间互调?

    Binder被设计为CS模式,其本身是不支持服务端主动调客户端的,但我们可以有一些曲线救国的方式,观察Parcel这个类,它是支持传递IBinder和IInterface接口的,我们可以在Client调用Server时将Client端定义的Binder服务传递至Server,Server端拿到这个Binder地址,就可以在服务端也创建一个BinderProxy,就可以像Client端调用它一样调用Client端的Binder,这时双方角色互换,应用进程启动时就是这样把自己的ApplicationThread 通过AMS传递给system_server进程的,所以system_server在应用启动并主动绑定AMS后就可以通过ApplicationThreadProxy来远程调用应用的方法从而管理应用了。

    观察Parcel传递IInterface这个方法

    public final void writeStrongInterface(IInterface val) {
      writeStrongBinder(val == null ? null : val.asBinder());
    }
    

    实际还是传递的 IBinder,这里回去看我们实现的Binder通信的代码,我们只让 Stub 实现了 IInterface ,然而我们希望在传递 IInterface 时不用去区分是 Stub 还是 Proxy,我们让 Proxy 也实现IInterface这个接口,现在Stub和Proxy都实现我们定义的IMyInterface和系统提供的IInterface这两个接口,所以我们让IMyInterface直接继承IInterface就好了,现在Proxy需要重写IInterface的asBinder,返回mRemote变量就好了。至此,我们完成一份完整的 Binder 封装代码,这份代码和我们编写 IMyInterface.aidl 文件编译后编译器为我们生成的 java 文件是一样的,下面是使用Android Studio编写aidl文件后编译生成的java文件:

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: /home/end/AndroidStudioProjects/Demo/demo2/src/main/aidl/com/xjh/end/demo2/IMyAidlInterface.aidl
     */
    package com.xjh.demo;
    // Declare any non-default types here with import statements
    
    public interface IMyAidlInterface extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements com.xjh.demo.IMyAidlInterface {
            private static final java.lang.String DESCRIPTOR = "com.xjh.demo.IMyAidlInterface";
    
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                //可以用于区分此次调用是进程内还是进程间,因为进程内调用的,Stub 子类对象也就是
                //服务端实例 的构造函数被调用过程中将 DESCRIPTOR 保存为了自己的成员变量,所以调用
                //obj.queryLocalInterface(DESCRIPTOR)得到的结果不为空(实例实现是返回Binder子类也            //是Stub子类对象本身),如果是代理Binder端,之前的代码可以看出BinderProxy类重新的方法            //直接返回就是null,这就可以区分当前调用是
                //进程内还是进程间了
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an com.xjh.demo.IMyAidlInterface interface,
             * generating a proxy if needed.
             */
            public static com.xjh.demo.IMyAidlInterface asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof com.xjh.demo.IMyAidlInterface))) {
                    return ((com.xjh.demo.IMyAidlInterface) iin);
                    //若是进程内调用,直接强转返回就可以
                }
                //若为进程间调用,需要用一个代理类封装,这个代理类封装了往Binder发消息的代码,使得调用
                //想进程内调用一样方便
                return new com.xjh.demo.IMyAidlInterface.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_method0: {
                        data.enforceInterface(DESCRIPTOR);
                        //调用子类实现
                        this.method0();
                        reply.writeNoException();
                        return true;
                    }
                    case TRANSACTION_method1: {
                        data.enforceInterface(DESCRIPTOR);
                        int _arg0;
                        _arg0 = data.readInt();
                        int _arg1;
                        _arg1 = data.readInt();
                        //调用子类实现
                        int _result = this.method1(_arg0, _arg1);
                        reply.writeNoException();
                        //向客户端写入返回值
                        //android.os.Parcel data 和 reply 都只是数据的载体,至于数据具体是怎么
                        //通过Binder发送的,先不关心
                        reply.writeInt(_result);
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
            //aidl生成的文件把Stub作为IMyAidlInterface接口的内部类,又把Proxy作为Stub的内部类,但是        // 是不是内部并没有关系,这两个内部类都是静态的,放在哪里都一样,只是对外隐藏了这个代理类,使得
            //使用者只能使用Stub.asInterface 来返回这个对象而不能直接使用这个类创建实例,使得对Binder
            //进程内和进程间的访问都被封装成接口访问,模糊两者的区别,体现了更好的封装。
            private static class Proxy implements com.xjh.demo.IMyAidlInterface {
                private android.os.IBinder mRemote;
    
                Proxy(android.os.IBinder remote) {
                    //就是BinderProxy类的实例
                    mRemote = remote;
                }
    
                @Override
                public android.os.IBinder asBinder() {
                    return mRemote;
                }
    
                public java.lang.String getInterfaceDescriptor() {
                    return DESCRIPTOR;
                }
                
                //Proxy类的作用就是封装了远程调用时想Binder写入数据的操作,
                //这里仍然以android.os.Parcel类对象作为数据载体
                @Override
                public void method0() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        //写入描述符,以通过服务端验证
                        _data.writeInterfaceToken(DESCRIPTOR);
                        //将数据写入Binder
                        //数据的写入最终就是写入Binder驱动,数据写入内核空间,而
                        //由于Binder的机制,有部分内核空间和用户空间的逻辑地址映射到了同一块物理地址,
                        //所以服务端进程不需要再把数据复制到用户空间,
                        //这也是Binder进行进程间通信效率高的原因之一,
                        //只经过了一次数据拷贝,而像Socket,则需要经过两次拷贝,
                        //先从A进程将数据写入内核空间,由于进程间在内核空间共享逻辑地址,
                        //所以B进程在内核空间也可以访问到这个数据,但由于没有像Binder一样做内存映射,
                        //进程的内核空间和用户空间的逻辑地址不共享,
                        //在B进程的用户空间就访问不到这个数据,
                        //所以还要从内核空间再将数据拷贝到B进程的用户空间,完成一次跨进程数据传递
                        mRemote.transact(Stub.TRANSACTION_method0, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
    
                @Override
                public int method1(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_method1, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.readInt();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
    
            static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
            static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        }
        //这就是Stub未实现需要子类自己实现的方法,子类实现这两个方法实现服务端逻辑
        public void method0() throws android.os.RemoteException;
    
        public int method1(int a, int b) throws android.os.RemoteException;
    }
    
    

    以上代码算是我们分析AIDL的原理分析,我们定义的AIDL文件最终会生成以上代码,可以看出数据在客户端写入和在服务端读出都是用了Parcel作为载体,这个类作用很强大,其还内部封装了对Serializable接口数据的Binder传递方法,我们会在另一篇文章中解读这个类。

    我们定义AIDL接口以后,只需要在Server端继承Stub并实现我们定义的接口方法,在客户端将传递过来的IBinder对象用 asInterface 方法封装,然后我们就可以像使用本地对象一样调用远程对象了。

    最终实现的效果,进程内调用时,就是直接调用实现类的方法(method0,method1),跨进程调用时,客户端通过Proxy类往代理Binder写值,在服务端进程里,再取出这些值,根据这些数据再去调用对应的 实现类的方法(method0,method1),在服务端,方法的调用是在Binder线程里进行的,远程调用的每次调用在服务端都是在Binder线程里进行的,这些Binder线程由Binder线程池管理,也就是说如果是远程调用,method0,method1是运行在Binder线程里的,那么如果想让我们的调用在Server端主线程执行,我们需要在Server端主线程创建Handler来把消息通过handler再转给主线程来实现,这是AMS管理应用组件生命周期的方式,这也是Android另一个进程间通信方式Messager的原理,其实就是封装了Binder和Handler,Binder跨进程发过来的消息立即转到Handler,再用Handler把消息从Binder线程转到指定线程,在指定线程中处理,这里我们即使不看代码也应该可以判断,Message的消息发送都是单边调用,消息一旦发出不会等待调用结果返回。

    要注意的是,如果我们是在App Process的UI thread 里面双边调用远程对象,显然和调用本地对象一样,这个调用不能是耗时操作,UI thread 会等待远端方法返回后再继续运行。

    这里贴一下以上java代码对应的AIDL文件,SDK就是根据这个文件生成了以上一大串java代码:

    // IMyAidlInterface.aidl
    package com.xjh.demo;
    
    // Declare any non-default types here with import statements
    
    interface IMyAidlInterface {
        void method0();
        int method1(int a, int b);
    }
    

    如何使用AIDL

    AIDL接口支持哪些数据类型

    • Java 基本数据类型
    • String和CharSequence
    • Parcelable
    • List 和 Map(泛型类型必须是以上类型)
    • AIDL接口

    aidl的使用需要我们声明对应的aidl接口,编译时SDK会根据aidl文件生成相应的java代码,其功能就是封装Binder的操作,使跨进程调用和本地调用一样方便,上面已经贴出传递基本类型的AIDL代码,而对于对象的传递我们需要用到Parcelable接口,而在aidl接口中的方法,其 Parcelable 类型的参数不管是不是属于同一个包都需要 import,aidl 接口也是,除此之外,形参还需要指定 in | out | inout 类型,基本类型默认且只能是in类型,out类型指Binder服务端不读取从客户端传过来的数据,而直接创建空对象,在这次调用的末尾再把这个对象再传递回客户端,inout就是接收客户端传过来的数据,生成对象,调用过后再把这个对象传递回去。

    所有需要在Binder中传递的 Parcelable 的实现类都需要创建与其名相同的 aidl 文件,这个文件中不必写接口和方法,如我要实现一个Book 类,实现Parcelable接口:

    package com.xxx.xxx;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Book implements Parcelable{
        public String name;
        public int price;
    
        protected Book(Parcel in) {
            name = in.readString();
            price = in.readInt();
        }
    
        public static final Creator<Book> CREATOR = new Creator<Book>() {
            @Override
            public Book createFromParcel(Parcel in) {
                return new Book(in);
            }
    
            @Override
            public Book[] newArray(int size) {
                return new Book[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeInt(price);
        }
    }
    
    
    // Book.aidl
    package com.xxx.xxx;
    parcelable Book;
    

    以上可知我们实现 Parcelable 这个接口,主要的工作就是把我们想要传递的对象包含的数据写入 Parcel 以及从Parcel中取出数据生成对应的对象,其实还是把对象拆成基本类型,再在另一端再次生成对象,Binder底层传递的还是基本类型,其实对于安卓的另一个序列化接口Serializable,Parcel也就会将其反射取出数据装入byte数组,然后在另一端取出来反射再生成对象,Binder底层传递的还是基本类型,我们将用一篇文章讲解Parcel这个类。

    复习下这张图:


    Binder.png

    相关文章

      网友评论

        本文标题:Binder使用

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