Android中的Binder跨进程通信机制与AIDL

在Code中,两个对象直接相互访问的前提是这两个对象都存在于相同的内存地址空间中,如果两个对象分别存在于两个不同的进程中,比如上面我们所说的ActivityManager和ActivityManagerService,那么这两个对象是不能直接调用的,就需要使用一种跨进程通信技术,使存在于两个不同进程的对象能够相互访问,传统的跨进程通信方式:Socket、信号量、管道、共享内存、消息队列等。

Binder具有的优势,传统的跨进程通信机制,如Socket,开销大且效率不高,而管道和队列拷贝次数多,更重要的是,对于移动设备来说,安全性相当重要,而传统的通信机制安全性低,大部分情况下接收方无法得到发送方进程的可信PID/UID,难以对其身份甄别。而Binder在设计的时候就考虑了以上各种缺点,在确保传输性能的同时还提高了安全性。

首先对Binder所涉及的4个主要模块作大致的了解,它们分别是Binder Client、Binder Server、ServerManager、Binder Driver。这4者之间的关系类似于网络访问,Binder Client相当于我们的客户端PC,Binder Server相当于服务器,ServerManager相当于DNS服务器,而Binder Driver相当于一个路由器。

 

 

其中Binder Driver实现在内核空间中,而其余的3者Binder Client、Binder Server、ServerManager实现在用户空间中。

 

Binder Driver位于内核空间中,其以字符设备中的misc类型注册,用户可以从/dev/binder设备文件节点上,通过 open 和 ioctl 文件操作函数 与 Binder Driver 进行通信,其主要负责Binder通信的建立,以及其在进程间的传递和Binder引用计数管理/数据包的传输等。而Binder Client与Binder Server之间的跨进程通信则统一通过Binder Driver处理转发,对于Binder Client来说,其只需要知道自己要使用的Binder的名字以及该Binder实体在ServerManager中的0号引用即可,访问的原理也比较简单,Binder Client先是通过0号引用去访问ServerManager获取该Binder的引用,得到引用之后就可以像普通方法那样调用Binder实体的方法。最后ServerManager则用来管理Binder Server,Binder Client可以通过它来查询Binder Server接口,刚才我们说到的Binder Client可以通过ServerManager来获取Binder引用,这个Binder引用就是由ServerManager来转换的,这里与其说是转换不如说是映射更为直接,Binder Server在生成一个Binder实体的同时会为其绑定一个名字,并将这个名字封装成一个数据包

传递给Binder Driver,Binder Driver接收到这个数据包后,如果发现这个Binder是新传递过来的,那么就会为其在内核空间中创建相应的Binder实体节点和一个对该实体节点的引用,这个实体节点在源码中叫做:Binder_node而其引用叫做Binder_ref,创建完毕后,Binder Driver就会将该引用传递给ServerManager,ServerManager收到后就会从中取出该Binder的名字和引用插入一张表中,这跟DNS中存储的域名到IP地址的映射原理类似。

 

而对于网络访问来说,而DNS服务器也并不一定对每一个IP地址都有域名映射的记录,我们常常也会碰到直接通过IP地址访问服务器的情况,而Binder也一样,并非一定要在ServerManager中有记录,很多时候Binder Server会将一个Binder实体封装进数据包传递给Binder Client,而此时Binder Server会在该数据包中标注Binder实体的位置, Binder Driver则会为该匿名的Binder生成实体节点和实体引用,并将该引用传递给 Binder Client。

 

Binder Driver负责的任务

 

 

Binder Client与Binder Server之间的跨进程通信则统一通过Binder Driver处理转发。

 

Binder Client先是通过0号引用去访问ServerManager获取该Binder的引用,得到引用之后就可以像普通方法那样调用Binder实体的方法。

 

ServerManager则用来管理Binder Server。

 

情况一

 

情况二

 

既然ServerManager的作用类似于DNS服务器,那么其本质是不是应该是一个Binder Server才对呢?答案是肯定的,ServerManager就是一个标准的Binder Server,并且在Android中约定其在Binder通信的过程中唯一标识(类似于IP地址)永远是0。

ServerManager既然是一个标准的Binder Server,那么也应该对外公布其可用的接口方法,你可将其看做一个服务器,既然能让客户端访问,总得给客户端可访问的接口和数据吧。

/**
 * Basic interface for finding and publishing system services.
 * 用于查找和发布系统服务的基础接口
 * An implementation of this interface is usually published as the
 * global context object, which can be retrieved via
 * BinderNative.getContextObject().  An easy way to retrieve this
 * is with the static method BnServiceManager.getDefault().
 * 此接口的实现通常作为全局上下文对象发布,可以通过BinderNative.getContextObject()检索该对象。 检索它的一种简单方法是使用静态方法BnServiceManager.getDefault()。
 * @hide
 */
public interface IServiceManager extends IInterface
{
    /**
     * Retrieve an existing service called @a name from the
     * service manager.  Blocks for a few seconds waiting for it to be
     * published if it does not already exist.
     */
    public IBinder getService(String name) throws RemoteException;
    
    /**
     * Retrieve an existing service called @a name from the
     * service manager.  Non-blocking.
     */
    public IBinder checkService(String name) throws RemoteException;

    /**
     * Place a new @a service called @a name into the service
     * manager.
     */
    public void addService(String name, IBinder service, boolean allowIsolated)
                throws RemoteException;

    /**
     * Return a list of all currently running services.
     */
    public String[] listServices() throws RemoteException;

    /**
     * Assign a permission controller to the service manager.  After set, this
     * interface is checked before any services are added.
     */
    public void setPermissionController(IPermissionController controller)
            throws RemoteException;
    
    static final String descriptor = "android.os.IServiceManager";

    int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
    int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
    int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
    int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
    int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
    int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
}

 

可以看到IServiceManager中所定义的方法并不多,因为我们的ServiceManager所承担的只是“DNS服务器”的角色,其作用如上面所说,只是简单地将Binder以字符表示的名字转换为一个引用。而对于在Binder Client端的ServiceManagerNative和ServcieManagerProxy(这个类在Android8.0中没有了)则都实现了该接口。

/**
 * Native implementation of the service manager.  Most clients will only
 * care about getDefault() and possibly asInterface().
*(服务管理器的本地实现。 大多数客户端只关心getDefault()和asInterface()。)
 * @hide
 */
public abstract class ServiceManagerNative extends Binder implements IServiceManager
{
    /**
     * Cast a Binder object into a service manager interface, generating
     * a proxy if needed.
     */
    static public IServiceManager asInterface(IBinder obj)
    {
        if (obj == null) {
            return null;
        }
        IServiceManager in =
            (IServiceManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }
        
        return new ServiceManagerProxy(obj);
    }
    
    public ServiceManagerNative()
    {
        attachInterface(this, descriptor);
    }
    
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
    {
        try {
            switch (code) {
            case IServiceManager.GET_SERVICE_TRANSACTION: {
                data.enforceInterface(IServiceManager.descriptor);
                String name = data.readString();
                IBinder service = getService(name);
                reply.writeStrongBinder(service);
                return true;
            }
    
            case IServiceManager.CHECK_SERVICE_TRANSACTION: {
                data.enforceInterface(IServiceManager.descriptor);
                String name = data.readString();
                IBinder service = checkService(name);
                reply.writeStrongBinder(service);
                return true;
            }
    
            case IServiceManager.ADD_SERVICE_TRANSACTION: {
                data.enforceInterface(IServiceManager.descriptor);
                String name = data.readString();
                IBinder service = data.readStrongBinder();
                boolean allowIsolated = data.readInt() != 0;
                addService(name, service, allowIsolated);
                return true;
            }
    
            case IServiceManager.LIST_SERVICES_TRANSACTION: {
                data.enforceInterface(IServiceManager.descriptor);
                String[] list = listServices();
                reply.writeStringArray(list);
                return true;
            }
            
            case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
                data.enforceInterface(IServiceManager.descriptor);
                IPermissionController controller
                        = IPermissionController.Stub.asInterface(
                                data.readStrongBinder());
                setPermissionController(controller);
                return true;
            }
            }
        } catch (RemoteException e) {
        }
        
        return false;
    }

    public IBinder asBinder()
    {
        return this;
    }
}

class ServiceManagerProxy implements IServiceManager {
    public ServiceManagerProxy(IBinder remote) {
        mRemote = remote;
    }
    
    public IBinder asBinder() {
        return mRemote;
    }
    
    public IBinder getService(String name) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }

    public IBinder checkService(String name) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        mRemote.transact(CHECK_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }

    public void addService(String name, IBinder service, boolean allowIsolated)
            throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        data.writeStrongBinder(service);
        data.writeInt(allowIsolated ? 1 : 0);
        mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
        reply.recycle();
        data.recycle();
    }
    
    public String[] listServices() throws RemoteException {
        ArrayList<String> services = new ArrayList<String>();
        int n = 0;
        while (true) {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            data.writeInterfaceToken(IServiceManager.descriptor);
            data.writeInt(n);
            n++;
            try {
                boolean res = mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0);
                if (!res) {
                    break;
                }
            } catch (RuntimeException e) {
                // The result code that is returned by the C++ code can
                // cause the call to throw an exception back instead of
                // returning a nice result...  so eat it here and go on.
                break;
            }
            services.add(reply.readString());
            reply.recycle();
            data.recycle();
        }
        String[] array = new String[services.size()];
        services.toArray(array);
        return array;
    }

    public void setPermissionController(IPermissionController controller)
            throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeStrongBinder(controller.asBinder());
        mRemote.transact(SET_PERMISSION_CONTROLLER_TRANSACTION, data, reply, 0);
        reply.recycle();
        data.recycle();
    }

    private IBinder mRemote;
}

public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }

    /**
     * Returns a reference to a service with the given name.
     * 
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(getIServiceManager().getService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    /**
     * Returns a reference to a service with the given name, or throws
     * {@link NullPointerException} if none is found.
     *
     * @hide
     */
    public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
        final IBinder binder = getService(name);
        if (binder != null) {
            return binder;
        } else {
            throw new ServiceNotFoundException(name);
        }
    }

    /**
     * Place a new @a service called @a name into the service
     * manager.
     * 
     * @param name the name of the new service
     * @param service the service object
     */
    public static void addService(String name, IBinder service) {
        try {
            getIServiceManager().addService(name, service, false);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }

    /**
     * Place a new @a service called @a name into the service
     * manager.
     * 
     * @param name the name of the new service
     * @param service the service object
     * @param allowIsolated set to true to allow isolated sandboxed processes
     * to access this service
     */
    public static void addService(String name, IBinder service, boolean allowIsolated) {
        try {
            getIServiceManager().addService(name, service, allowIsolated);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }
    
    /**
     * Retrieve an existing service called @a name from the
     * service manager.  Non-blocking.
     */
    public static IBinder checkService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(getIServiceManager().checkService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in checkService", e);
            return null;
        }
    }

    /**
     * Return a list of all currently running services.
     * @return an array of all currently running services, or <code>null</code> in
     * case of an exception
     */
    public static String[] listServices() {
        try {
            return getIServiceManager().listServices();
        } catch (RemoteException e) {
            Log.e(TAG, "error in listServices", e);
            return null;
        }
    }

    /**
     * This is only intended to be called when the process is first being brought
     * up and bound by the activity manager. There is only one thread in the process
     * at that time, so no locking is done.
     * 
     * @param cache the cache of service references
     * @hide
     */
    public static void initServiceCache(Map<String, IBinder> cache) {
        if (sCache.size() != 0) {
            throw new IllegalStateException("setServiceCache may only be called once");
        }
        sCache.putAll(cache);
    }

    /**
     * Exception thrown when no service published for given name. This might be
     * thrown early during boot before certain services have published
     * themselves.
     *
     * @hide
     */
    public static class ServiceNotFoundException extends Exception {
        public ServiceNotFoundException(String name) {
            super("No service published for: " + name);
        }
    }

 

Binder Server端是由C来实现的,即使两者使用语言不同,但在Binder Server中所定义的接口函数依然还是与Binder Client一致。其代码位置在:W:\Android8.1_intergration_2\frameworks\native\cmds\servicemanager\server_manager.c中。

 

Android的Binder机制是一个庞大的体系模块,可以毫不夸张的说,整个Android系统之所以能够有条不紊的运行,很大程度依赖于Binder机制。(View的底层通信实现也会依赖于Binder)。

 

如何使用Binder进行跨进程通信?

整个Binder机制由四大部分组成,而对于底层的Binder Driver和ServerManager来说我们没哟必要去实现,因为这部分逻辑android已经帮我们封装好了,我们只需要实现自己的Binder Client和Binder Server即可,因为BInder Server部分代码在C语言中实现,而且逻辑非常复杂,android提供了一个简便的方式来生成Binder Server端,这种方式就是AIDL, Android Interface Description Language,即android接口描述语言。

参考《Android源码设计模式》


版权声明:本文为zhangying1994原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。