android之binder学习攻克

Android Binder机制

2018-10-03  本文已影响1632人  JimmieYang

Binder简介

Binder简介 Binder驱动 模型原理图

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() :

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

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

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

getUserById(long id)方法 :

请看 完整的实现:

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机制 原理
上一篇下一篇

猜你喜欢

热点阅读