Binder使用

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

推荐阅读更多精彩内容

  • 毫不夸张地说,Binder是Android系统中最重要的特性之一;正如其名“粘合剂”所喻,它是系统间各个组件的桥梁...
    weishu阅读 17,820评论 29 246
  • 原文:http://weishu.me/2016/01/12/binder-index-for-newer/ 要点...
    指尖流逝的青春阅读 2,603评论 0 13
  • Jianwei's blog 首页 分类 关于 归档 标签 巧用Android多进程,微信,微博等主流App都在用...
    justCode_阅读 5,900评论 1 23
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 临近年节,朋友圈的一条短语引起了莫名的唏嘘,原来我们一直都是骗子,从小到大,而且只对最亲近的人…… 1.流鼻涕的年...
    海飞廉阅读 451评论 6 21