Android Binder机制

作者: JimmieYang | 来源:发表于2018-10-03 20:47 被阅读1632次

    Binder简介

    Binder简介 Binder驱动
    • binder使用内存映射(mmap)来实现进程间传递数据,比较传统的进程间通信.

    • binder只需要进行一次的数据拷贝(copy_from_user()).

    • 传统进程通信需要经过两次数据拷贝(copy_from_user(),copy_to_user())

    模型原理图

    Binder跨进程通信的调用过程

    Binder通信
    1. client进程将数据写入 Parcelable对象中
    2. client进程通过代理的Binder对象的transact()方法将数据传递给Binder驱动.
    3. client进程中的调用线程被挂起(直到server进程返回结果)
    4. Binder驱动根据Binder代理对象找到该真正的Binder对象对应的server进程.
    5. Binder驱动把数据发送到server进程中,并通知server进程执行解包
    6. server进程从Binder线程池中取出目标线程.
    7. server进程通过 onTransact()回调进行数据解包
    8. server进程执行目标方法
    9. server进程返回数据
    10. Binder驱动通知Binder代理对象返回结果
    11. client进程中的执行线程被唤醒
    12. client进程通过Binder代理对象接收到返回结果

    说明 :

    服务端的Binder指的是Binder的本地对象
    客户端的Binder指的是Binder代理对象,它只是Binder本地对象的一个远程代理.

    一个例子来解释上述的binder机制

    通过aidl自动生成的java类来分析binder机制.

    1. 使用Parcelable来定义一个传输数据类.
    package cn.jimmie.learn.art.ipc.aidl
    
    import android.os.Parcel
    import android.os.Parcelable
    
    class User : Parcelable {
        var id: Long = 0
        var name: String? = ""
        var age = 0
    
        constructor(parcel: Parcel) {
            id = parcel.readLong()
            name = parcel.readString()
            age = parcel.readInt()
        }
    
        constructor(id: Long, name: String, age: Int) {
            this.id = id
            this.name = name
            this.age = age
        }
    
        override fun writeToParcel(parcel: Parcel, flags: Int) {
            parcel.writeLong(id)
            parcel.writeString(name)
            parcel.writeInt(age)
        }
    
        override fun describeContents(): Int = 0
    
        companion object CREATOR : Parcelable.Creator<User> {
            override fun createFromParcel(parcel: Parcel): User = User(parcel)
            override fun newArray(size: Int): Array<User?> = arrayOfNulls(size)
        }
    }
    
    1. 使用 aidl文件声明 跨进程对象
    // User.aidl
    package cn.jimmie.learn.art.ipc.aidl;
    
    parcelable User;
    
    1. 使用 aidl 编写数据传输接口.
    // IUserManager.aidl
    package cn.jimmie.learn.art.ipc.aidl;
    import cn.jimmie.learn.art.ipc.aidl.User;
    
    // Declare any non-default types here with import statements
    
    interface IUserManager {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        User getUserById(long id);
    }
    

    需要注意的是 非基本类型的数据,需要使用import导入,不管是否在同一个包中.

    非基本类型的参数,需要使用 in,out,inout来标记. 该标记表示数据的流向.

    in 表示从客户端流向服务端的数据.

    out 表示从服务端流向客户端的数据.

    您应该将方向限定为真正需要的方向,因为编组参数的开销极大。

    到此,对 aidl进行编译,会得到一份自动生成的IUserManager.java文件.

    接下来分析,这份自动生成的文件内容.

    自动生成的类方法剖析

    服务端使用的类-->Stub:

    Stub是一个继承IBinder和实现 数据接口的抽象方法.

    服务端需要实现该方法,来创建一个真正的binder对象.

    onTransact()方法 :

    该方法是在客户端和服务端不在同一个进程中,才会被调用.用于传递客户端定位方法和参数.
    该方法是在binder驱动中binder线程池分配的一个线程中运行,服务端可以接收到客户端传来的方法参数和定位到调用的方法.
    服务端在根据这些参数,来调用真实的服务端数据接口.

    注意,客户端的调用线程会等待服务端调用结束,得到数据返回

    客户端通过Stub的静态方法asInterface来获取数据接口:

    Stub.asInterface() :

    • 该静态方法是提供给客户端调用,用来获取所需的数据接口对象.
    • 如果客户端和服务端在同一个进程,那么客户端服务端共享一个binder,则直接返回 Stub本身

    注意此时客户端和服务端操作的是同一个对象.

    • 如果客户端和服务端在不同的进程中,那么返回一个服务端binder的代理对象.

    注意此时客户端和服务端操作的不是同一个对象.

    • 客户端通过代理对象把需要调用的方法和参数数据传递给binder驱动,binder驱动通知服务端调用真正的的binder方法.
    • 然后将调用的返回值再传递给binder驱动,binder驱动又将数据的返回给代理对象.
    • 这样客户端就能从代理对象中获取跨进程数据的返回

    代理对象的数据接口实现:

    getUserById(long id)方法 :

    • 跨进程过程中,客户端调用的数据接口方法
    • 该方法将方法调用的参数 传递给binder驱动进而传递给远程服务的binder对象
    • 远程服务器进而调用真正的数据接口方法,此时会阻塞当前线程,直到远程服务把返回值通过binder驱动写入到reply中

    请看 完整的实现:

    package cn.jimmie.learn.art.ipc.aidl;
    // Declare any non-default types here with import statements
    
    public interface IUserManager extends android.os.IInterface {
        /**
         * 服务端需要实现 Stub类,来创建一个真正的binder对象
         */
        public static abstract class Stub extends android.os.Binder implements cn.jimmie.learn.art.ipc.aidl.IUserManager {
            private static final java.lang.String DESCRIPTOR = "cn.jimmie.learn.art.ipc.aidl.IUserManager";
    
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * 该静态方法是提供给客户端调用,用来获取所需的数据接口对象.
             * <p>
             * 如果客户端和服务端在同一个进程,那么客户端服务端共享一个binder,直接返回 共享的数据接口,
             * 注意此时客户端和服务端操作的是同一个对象.
             * <p>
             * 如果客户端和服务端在不同的进程中,那么返回一个服务端binder的代理对象.
             * 注意此时客户端和服务端操作的不是同一个对象.
             * <p>
             * 客户端通过代理对象把需要调用的方法和参数数据传递给binder驱动,binder驱动通知服务端调用真正的的binder方法.
             * 然后将调用的返回值再传递给binder驱动,binder驱动又将数据的返回给代理对象.
             * 这样客户端就能从代理对象中获取跨进程数据的返回
             */
            public static cn.jimmie.learn.art.ipc.aidl.IUserManager asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                // 判断是同一进程,则直接返回服务端的调用接口
                if (((iin != null) && (iin instanceof cn.jimmie.learn.art.ipc.aidl.IUserManager))) {
                    return ((cn.jimmie.learn.art.ipc.aidl.IUserManager) iin);
                }
                // 使用代理返回调用接口
                return new cn.jimmie.learn.art.ipc.aidl.IUserManager.Stub.Proxy(obj);
            }
    
            @Override
            public android.os.IBinder asBinder() {
                return this;
            }
    
            /**
             * 如果客户端和服务端在同一个进程 , 将不会回调到该函数.
             * 因为 在同一个进程将不会调用 Proxy类,代理类中 调用 `transact()`函数, 服务端将回调此函数
             */
            @Override
            public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
                java.lang.String descriptor = DESCRIPTOR;
                // 根据code来判断远程调用的方法
                switch (code) {
                    case INTERFACE_TRANSACTION: {
                        reply.writeString(descriptor);
                        return true;
                    }
                    case TRANSACTION_getUserById: {
                        // 该方法和 writeInterfaceToken()方法,配合进行数据有效性验证
                        data.enforceInterface(descriptor);
                        // 获取传递的方法参数
                        long _arg0 = data.readLong();
                        // 调用服务端binder中真实有效的方法
                        cn.jimmie.learn.art.ipc.aidl.User _result = this.getUserById(_arg0);
                        reply.writeNoException();
                        if ((_result != null)) {
                            // 返回数据有效,写入标志位
                            reply.writeInt(1);
                            // 将返回的数据写入到 reply中
                            _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                        } else {
                            // 返回数据无效,写入标志位
                            reply.writeInt(0);
                        }
                        return true;
                    }
                    default: {
                        return super.onTransact(code, data, reply, flags);
                    }
                }
            }
    
            /**
             * 数据接口的代理对象,当需要进行跨进程通信时,客户端的调用对象
             */
            private static class Proxy implements cn.jimmie.learn.art.ipc.aidl.IUserManager {
                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;
                }
    
                /**
                 * 跨进程过程中,客户端调用的数据接口方法
                 * 该方法将方法调用的参数 传递给binder驱动进而传递给远程服务的binder对象
                 * 远程服务器进而调用真正的数据接口方法,此时会阻塞当前线程,直到远程服务把返回值通过binder驱动写入到reply中
                 */
                @Override
                public cn.jimmie.learn.art.ipc.aidl.User getUserById(long id) throws android.os.RemoteException {
                    // 方法参数
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    // 方法返回值
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    // 返回的结果数据
                    cn.jimmie.learn.art.ipc.aidl.User _result;
                    try {
                        // 写入描述符,用于数据验证
                        _data.writeInterfaceToken(DESCRIPTOR);
                        // 写入参数
                        _data.writeLong(id);
                        // 传递参数数据和返回数据到远程,此时当前线程将被阻塞,等待远程调用结束
                        mRemote.transact(Stub.TRANSACTION_getUserById, _data, _reply, 0);
                        _reply.readException();
                        if ((0 != _reply.readInt())) {
                            // 数据正确返回,将其写入到_result中
                            _result = cn.jimmie.learn.art.ipc.aidl.User.CREATOR.createFromParcel(_reply);
                        } else {
                            _result = null;
                        }
                    } finally {
                        // 数据回收
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
    
            static final int TRANSACTION_getUserById = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        }
    
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        public cn.jimmie.learn.art.ipc.aidl.User getUserById(long id) throws android.os.RemoteException;
    }
    

    至此, binder机制的运转流程已经很明了了.

    可以看出, aidl 是非必须的, 只要我们自己编写 IUserManager接口,也能过实现binder跨进程通信的功能.

    参考

    1. Android开发艺术探索(任玉刚)
    2. Android跨进程通信:图文详解 Binder机制 原理

    相关文章

      网友评论

        本文标题:Android Binder机制

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