Binder机制

1,199 阅读11分钟

Binder,英文意思是别针、回形针。在Android中用于完成进程间通信(IPC),即把多个进程“别”在一起。比如,普通应用程序可以调用音乐播放服务提供的播放、暂停、停止等功能。

Binder框架

Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块。

服务端:一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程。该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行到Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务代码。因此,要实现一个Binder服务,就必须重载onTransact()方法。

Binder驱动:任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。客户端要访问远程服务时,都是通过mRemote对象。

应用程序客户端:客户端要想访问远程服务,必须获取远程服务在Binder对象中对应的mRemote引用。获得该mRemote对象后,就可以调用transact()方法,而在Binder驱动中,mRemote对象也重载了transact()方法,重载的内容主要包括以下几项。

  1. 以线程间消息通信的模式,向服务端发送客户端传递过来的参数
  2. 挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知(notify)
  3. 接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区

从这里可以看出,对应用程序开发员来讲,客户端似乎是直接调用远程服务对应的Binder,而事实上则是通过Binder驱动进行了中转。即存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,所不同的是Binder驱动中的对象不会再额外产生一个线程。

设计Server端

public class MusicPlayerService extends Binder {
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        return super.onTransact(code, data, reply, flags);
    }

    public void start(String filePath) {
    }

    public void stop() {
    }
}

初始化MusicPlayerService时,会多一个线程。

code:用于标识客户端期望调用服务的哪个函数,因此,双方需要约定一组int值,不同的值,代表不同的服务端函数,该值和客户端的transact()函数中第一个参数code的值是一致的。这里假定1000是双方约定要调用start()函数的值。

data:数据,当约定的code定义好之后,就可以知道data放的是什么数据。

reply:服务端把返回的结果放在其中。

flag:执行IPC调用的模式,分为两种:一种是双向,用常量0表示,其含义是服务端执行完指定服务后会返回一定的数据;另一种是单向,用常量1表示,其含义是不返回任何数据。

onTransact(int code, Parcel data, Parcel reply, int flags) {
    switch (code){
        case 1000:
            data.enforceInterface("MusicPlayerService");
            String filePath = data.readString();
            start(filePath);
            break;
    }
}

enforceInterface()是为了某种校验,它与客户端的writeInterfaceToken()对应

Binder客户端设计

    IBinder mRemote = null;
    // 获取mRemote对象
    String filePath = "/sdcard/music/xxx.mp3";
    int code = 1000;
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken("MusicPlayerService");
    data.writeString(filePath);
    mRemote.transact(code, data, reply, 0);
    IBinder binder = reply.readStrongBinder();
    reply.recycle();
    data.recycle(); 

使用步骤:

  1. 申明code,客户端与服务端的协议。
  2. 获取data,用于放置数据。可以放入原子类型或者继承Parcel的类。注意获取方式是Parcel.obtain()
  3. 获取reply,同data。
  4. writeInterfaceToken(),标注远程服务的名称,不是必需的,申明只是为了确保调用的指定的客户端。
  5. data写入数据,writeString(),写入数据的顺序也是和服务端约定好的。
  6. 调用transact()

使用Service类

Binder服务端和客户端存在两个重要的问题

  1. 客户端如何获取服务端的Binder对象应用
  2. 客户端和服务端必须事先约好两件事情
    • 服务端函数的参数在包裹中的顺序
    • 服务端不同函数的int标识

获取Binder对象

AMS提供了startService()用于客户端启动服务,要获取Binder对象,需要bindService(Intent service, ServiceConnection conn, int flags)

public interface ServiceConnection {
    public void onServiceConnected(ComponentName name, IBinder service);
    public void onServiceDisconnected(ComponentName name);
}

调用流程如下:

保证包裹内参数顺序aidl工具的使用

Android的SDK中提供了一个aidl工具,该工具可以把一个aidl文件转换为一个java类文件,在该java类文件,同时重载了transact和onTransact()方法,统一了存入包裹和读取包裹参数,从而使设计者可以把注意力放到服务代码本身上。

package com.kongge.test
interface IMusicPlayerService {
    boolean start(String filePath);
    void stop();
}

文件名IMusicPlayerService.aidl,经过编译后会生成IMusicPlayerService.java文件,aidl语法基本类似Java,package指定输出后的Java文件对应的报名,包裹类只能写入三种类型数据。

  1. java原子类型,如int、long、String等变量
  2. Binder引用
  3. 实现了parcelable的对象

生成的IMusicPlayerService.java

package com.kongge.test
public interface IMusicPlayerService extends android.os.IInterface 
{
    public static abstract class Stub extends android.os.Binder implements com.kongge.test.IMusicPlayerService
    {
        public static IMusicPlayerService asInterface(IBinder obj){...}
        pubilc IBinder asBinder(){...}
        public boolean OnTransaction(int code, Parcel data, Parcel reply, int flags){...}
        
        private static class Proxy implements com.kongge.test.IMusicPlayerService
        {
            private IBinder mRemote;
            public IBinder asBinder() {retrun mRemote}
            public String getInterfaceDescriptor(){...};
            public boolean start(String filePath){...} // aidl文件定义的方法
            public void stop(){...} // aidl文件定义的方法
        }
        static final int TRANSACTION_start = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    public boolean start(java.lang.String filePath) throws android.os.RemoteException;
    public void stop() throws android.os.RemoteException;
}

这些代码主要完成以下三个任务

  1. 定义一个Java interface,内部包含aidl文件所声明的服务函数,类名称为IMusicPlayerService,并且该类基于IInterface接口,即需要提供一个asBinder()函数。
  2. 定义一个Proxy类,该类将作为客户端程序访问服务端的代理。所谓的代理主要就是为了前面所提到的第二个重要问题—统一包裹内写入参数的顺序。
  3. 定义一个Stub类,这是一个abstract类,基于Binder类,并且实现了IMusicPlayerService接口,主要由服务端来使用。该类之所以要定义为一个abstract类,是因为具体的服务函数必须由程序员实现,因此,IMusicPlayerService接口中定义的函数在Stub类中可以没有具体实现。同时,在Stub类中重载了onTranstact()方法,由于transact()方法内部给包裹内写入参数的顺序是由aidl工具定义的,因此,在onTransact()方法中,aidl工具自然知道应该按照何种顺序从包裹中取出相应参数。

在Stub类中,除了以上所述的任务外,tub还提供了一个asInterface()函数,提供这个函数的原因是服务端提供的服务除了其他进程可以使用外,在服务进程内部的其他类也可以使用该服务,对于后者,,显然是不需要经过IPC调用,而可以直接在进程内部调用的,而Binder内部有一个queryLocalInterface(String description)函数,该函数是根据输入的字符串判断该Binder对象是一个本地的Binder引用。在图一所示中曾经指出,当创建一个Binder对象时,服务端进程内部创建一个Binder对象,Binder驱动中也会创建一个Binder对象。如果从远程获取服务端的Binder,则只会返回Binder驱动中的Binder对象,,而如果从服务端进程内部获取Binder对象,则会获取服务端本身的Binder对象。

因此,asInterface()函数正是利用了queryLocalInterface()方法,提供了一个统一的接口。无论是远程客户端还是本地端,当获取Binder对象后,要以把获取的Binder对象作为asInterface()的参数,从而返回一个IMusicPlayerService接口,该接口要么使用Proxy类,要么直接使用Stub所实现的相应服务函数。

系统服务中的Binder对象

在应用程序编程时,经常使用getSystemService(String serviceName)方法获取一个系统服务,那么,这些系统服务的Binder引用是如何传递给客户端的呢?须知系统服务并不是通过startService()启动的。

getSystemService()函数的实现是在ContextImpl类中,该函数所返回的Service比较多,具体可参照源码。这些Service一般都由ServiceManager管理。

ServiceManager管理的服务

ServiceManager是一个独立进程,其作用如名称所示,管理各种系统服务,管理的逻辑如下图所示.

ServiceManager本身也是一个Service,Framework提供了一个系统函数,可以获取该Service对应的Binder引用,那就是BinderInternal.getContextObject()。该静态函数返回ServiceManager后,就可以通过ServiceManager提供的方法获取其他系统Service的Binder引用。这种设计模式在日常生活中到处可见,ServiceManager就像是一个公司的总机,这个总机号码是公开的,系统中任何进程都可以使用BinderInternal.getContextObject()获取该总机的Binder对象,而当用户想联系公司中的其他人(服务)时,则要经过总机再获得分机号码。这种设计的好处是系统中仅暴露一个全局Binder引用,那就是ServiceManager,而其他系统服务则可以隐藏起来,从而有助于系统服务的扩展,以及调用系统服务的安全检查。其他系统服务在启动时,首先把自己的Binder对象传递给ServiceManager,即所谓的注册(addService)。

下面从代码实现来看以上逻辑。可以查看ContextImpl.getSystemService()中各种Service的具体获取方法,比如INPUT_METHOD_SERVICE,代码如下:

} else if (INPUT_METHOD_SERVICE.equals(name)) {
    return InputMethodManager.getInstance(this);

而InputMethodManager.getInstance(this)的关键代码如下:

synchronized (InputMethodManager.class) {
    if (sInstance == null) {
        IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
        IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
        sInstance = new InputMethodManager(service, Looper.getMainLooper());
    }
    return sInstance;
}

即通过ServiceManager获取InputMethod Service对应的Binder对象b,然后再将该Binder对象作为IInputMethodManager.Stub.asInterface()的参数,返回一个IInputMethodManager的统一接口。

ServiceManager.getService()的代码如下:

public static IBinder getService(String name) {
    try {
        IBinder service = sCache.get(name);
        if (service != null) {
            return service;
        } else {
            return getIServiceManager().getService(name);
        }
    } catch (RemoteException e) {
        Log.e(TAG, "error in getService", e);
    }
    return null;
}

即首先从sCache缓存中查看是否有对应的Binder对象,有则返回,没有则调用getIServiceManager().getService(name),第一个函数getIServiceManager()即用于返回系统中唯一的ServiceManager对应的Binder,其代码如下:

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

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

以上代码中,BinderInternal.getContextObject()静态函数即用于返回ServiceManager对应的全局Binder对象,该函数不需要任何参数,因为它的作用是固定的。从这个角度来看,这个函数的命名似乎更应用明确一些,比如,可以命名为getServiceManager()。

其他所有通过ServiceManager获取的系统服务的过程与以上基本类似,所不同的就是传递给ServiceManager的服务名称不同,因为ServiceManager正是按照服务的名称(String类型)来保存不同的Binder对象的。

关于使用addService()向ServiceManager中添加一个服务一般是在SystemService进程启动时完成的。

理解Manager

ServiceManager所管理的所有Service都是以相应的Manager返回给客户端。因此,这里简述一下Framework中关于Manager的语义。在我们中国的企业里,Manager一般指经理,比如项目经理,人事经理,部门经理。经理本身的含义比较模糊,其角色有些是给我们分配任务,比如项目经理;有些是给我们提供某种服务,比如人事经理;有些则是监督我们的工作等。而在Android中,Manager的含义更应该翻译为经纪人,Manager所manager的对象是服务本身,因为每个具体的服务一般都会提供多个API接口,而Manager所manager的正是这些API。客户端一般不能直接通过Binder引用去访问具体的服务,而是要经过一个Manager,相应的Manager类对客户端是可见。而远程的服务类客户端则是隐藏的。

而这些Manager的类内部都会有一个远程服务Binder的变量,而且在一般情况下,这些Manager的构造函数参数中会包含这个Binder对象。简单地讲,即先通过ServiceManager获取远程服务的Binder引用,然后使用这个Binder引用构造一个客户端本地可以访问的经纪人,然后客户端就可以通过该经纪人访问远程的服务。

这种设计的作用是屏蔽直接访问远程服务,从而可以给应用程序提供灵活的,可控的API接口,比如AMS.。系统不希望用户直接去访问AMS,而是经过ActivityManager类去访问,而ActivityManager内部提供了一些更具可操作性的数据结构,比如RecentTaskInfo数据类封装了最近访问过的Task列表,MemoryInfo数据类封装了和内存相关的信息。

通过本地Manager访问远程服务的模型如下图所示:


copy from -----> 《Android内核剖析》