×

Android:从源码角度来赏析Binder机制的优美

96
SupKing_a520
2018.09.20 14:45* 字数 3666

谈到android进程间通信,就不得不想到Binder,那么他到底是何方圣神呢?话不多说,咱们下面一起来解密一番!

IBinder

首先我们来看一下Binder的声明:

public class Binder implements IBinder {...}

哟,那么IBinder又是什么呢?

public interface IBinder {
    
    int FIRST_CALL_TRANSACTION  = 0x00000001;
    int LAST_CALL_TRANSACTION   = 0x00ffffff;
    int PING_TRANSACTION        = ('_'<<24)|('P'<<16)|('N'<<8)|'G';
    int DUMP_TRANSACTION        = ('_'<<24)|('D'<<16)|('M'<<8)|'P';
    int SHELL_COMMAND_TRANSACTION = ('_'<<24)|('C'<<16)|('M'<<8)|'D';
    int INTERFACE_TRANSACTION   = ('_'<<24)|('N'<<16)|('T'<<8)|'F';
    int TWEET_TRANSACTION       = ('_'<<24)|('T'<<16)|('W'<<8)|'T';
    int LIKE_TRANSACTION        = ('_'<<24)|('L'<<16)|('I'<<8)|'K';
    int SYSPROPS_TRANSACTION    = ('_'<<24)|('S'<<16)|('P'<<8)|'R';
    int FLAG_ONEWAY             = 0x00000001;

    public static final int MAX_IPC_SIZE = 64 * 1024;
    public @Nullable String getInterfaceDescriptor() throws RemoteException;
    public boolean pingBinder();
    public boolean isBinderAlive();
    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor);
    public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException;
    public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException;
    public void shellCommand(@Nullable FileDescriptor in, 
            @Nullable FileDescriptor out, @Nullable FileDescriptor err,
            @NonNull String[] args, @Nullable ShellCallback shellCallback,
            @NonNull ResultReceiver resultReceiver) throws RemoteException;
    public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException;
    public interface DeathRecipient {
        public void binderDied();
    }
    public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException;
    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
}

哦,原来IBinder 是一个定义了跨进程传输能力的接口。只要实现了这个接口,就能将这个对象进行跨进程传递。
这些方法中重点关注 transact(),Binder里面与它对应的是 Binder.onTransact()


Binder

Binder是官方提供的实现了IBinder接口的操作,它是 Android IPC 的基础,平常接触到的各种 Manager(ActivityManager, ServiceManager 等),以及绑定 Service 时都在使用它进行跨进程操作。
下面介绍几个关键方法:

  • transact()
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
    if (false) Log.v("Binder", "Transact: " + code + " to " + this);

    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

连接Binder驱动,发起IPC请求。

  • onTransact()
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
    if (code == INTERFACE_TRANSACTION) {
        ......
    } else if (code == DUMP_TRANSACTION) {
        ......
    } else if (code == SHELL_COMMAND_TRANSACTION) {
        ......
    }
    return false;
}

根据 code 对传入的参数 data 做相应的处理,然后写入 reply,这样就能返回操作后的数据。

  • attachInterface()
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
}

这个方法的作用是将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个 IInterface,那 IInterface 又是什么呢?

public interface IInterface{
    public IBinder asBinder();
}

IInterface 里只定义了一个 asBinder() 方法,这个方法可以返回当前接口关联的 Binder 对象。


Binder通信机制

上面只是简单介绍了Binder类相关的信息,具体的详细信息可以去查看源码,但这只是Binder通信机制的基础。
要理解Binder通信机制,首先我们要先来了解一下Android的内存空间:


Linux内核模型.png

用户空间:用户空间的进程是相互独立的,彼此之间不能共享
内核空间:内核空间的内存是可以共享的,其大小可以通过参数来配置
Android进程间通信其实质就是利用内核空间共享来完成的。

下面我们来看Binder进程间通信的四个重要角色:


Binder通信的四个角色.png
  • Client:用户端进程
  • Server:服务端进程
  • Service Manager:运行在用户空间,它负责管理 Service 注册与查询等操作
  • Binder驱动:负责进程之间Binder通信的建立,进程间通信的核心

下面我们来总结一下Binder机制的整个运行过程

  1. ServiceManager 初始化
  2. Server 向 ServiceManager 注册自己
  3. Client 获取远程服务
    • Client 向 Server 发起 IPC 请求
    • Binder 驱动将该请求转发给 ServiceManager 进程,
    • ServiceManager 查找到 对应的 Server 的 Binder 引用后通过 Binder 驱动反馈给 Client(并不是实际真实的远程Binder对象,而是Binder驱动里的一个映射)
    • Client 收到 Server 对应的 Binder 引用后,会创建一个 Server 对应的远程服务(即 Server 在当前进程的代理)
  4. Client 通过代理调用 Server
    • Client 会先将请求数据从用户空间拷贝到内核空间,然后调用transact去连接Binder驱动
    • 接着Binder驱动会去连接远程进程,并通知远程进程执行onTransact()函数
  5. Server 返回结果
    • 执行完成后将结果写入内核空间
    • Binder驱动再唤醒客户端线程获取数据

注意:这个过程中客户端当前线程会被挂起!因此,如果远程进程是执行长时间的运算,请不要使用主线程去调用远程函数,以防止ANR。


示例分析

上面讲了一些理论上的Binder进程间通信的知识,可以清晰的看出Binder采用的是C/S架构的模式来进行进程间通信的,而其核心点也就是内核空间的Binder驱动。下面呢,我们会通过一个具体的实例从源码的角度来解析Binder的进程间通信。

首先我们这里给出一副价值200万的图(一点也不夸张哈!)


Binder通信架构.png
  1. AIDL(Android Interface Definition Language),即Android接口定义语言。通俗来讲就是定义一个大家都懂的语言,这样进程间交流才不会又障碍。
  2. Stub:接收底层C/C++Binder引用的回调。
  • function:就是我们定义的一些接口方法。
  • Proxy:用来访问底层的Binder驱动。
  1. 本地服务:就是在我们当前进程里的服务(后面会有具体的涉及)。
  2. Binder驱动:这里看图就行了,不做过多解释。

OK!价值200万的图大家赶紧收好。接下来我们就具体来分析源码。


一、AIDL创建
  1. 新建com.wf.testaidl.bean.Person类,并实现Parcelable
public class Person implements Parcelable {...}
  1. 在main下面新建aidl文件夹并创建aidl文件(注意:包名和类名必须相同
目录结构.png
  1. 编写aidl接口
// Person.aidl
package com.wf.testaidl.bean;

// Declare any non-default types here with import statements
parcelable Person;

Preson.aidl做一下映射避免下面文件编译的时候报错。

// PersonAidl.aidl
package com.wf.testaidl;

// Declare any non-default types here with import statements
import com.wf.testaidl.bean.Person;

interface PersonAidl {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    /*void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);*/

    void addPerson(in Person person);
    List<Person> getPersonList();
}

这个文件就具体定义了我们需要的接口。

  1. Make Project
    编译成功后会生成app\build\generated\source\aidl\debug\com\wf\testaidl\PersonAidl.java文件。


二、解析PersonAidl.java源码
package com.wf.testaidl;
public interface PersonAidl extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.wf.testaidl.PersonAidl {
        ...
        private static class Proxy implements com.wf.testaidl.PersonAidl {...}
        ...
    }
        
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     *//*void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);*/
    public void addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException;
    public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException;
}

是不是很熟悉,没错就是我们上面那幅200万的图。这里可以很清晰的看出PersonAidl里面包含了我们定义的接口方法和一个静态内部抽象类Stub,这个Stub继承了Binder并且实现了PersonAidl接口,这个Stub里面又定义了一个Proxy同样实现了PersonAidl接口。


Stub
ok!接下来我们来看看这个神奇Stub。

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.wf.testaidl.PersonAidl {
        private static final java.lang.String DESCRIPTOR = "com.wf.testaidl.PersonAidl";

        public Stub() {...}

        /**
         * Cast an IBinder object into an com.wf.testaidl.PersonAidl interface,
         * generating a proxy if needed.
         */
        public static com.wf.testaidl.PersonAidl asInterface(android.os.IBinder obj) {...}

        @Override
        public android.os.IBinder asBinder() {...}

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {...}

        private static class Proxy implements com.wf.testaidl.PersonAidl {...}
        
        static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

上面就是这个Stub的整个的一个结构,这里有两个方法名映射的int常量,这个后面我们会用到。
下面我们就来一步一步的分析它里面的代码。

  1. DESCRIPTOR
private static final java.lang.String DESCRIPTOR = "com.wf.testaidl.PersonAidl";

这个很简单吧,就是用一个静态常量来存储了我们AIDL的类名,有什么用呢?后面你就知道了。

  1. Stub() 构造函数
public Stub() {
    this.attachInterface(this, DESCRIPTOR);
}

构造函数里面就调了一个方法,而这个attachInterface自己没有,那么是谁的呢?没错,是它爹Binder的。

private IInterface mOwner;
private String mDescriptor;

public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}

那么,这个方法干了一个什么事儿呢?它就是把自己和这个DESCRIPTOR保存在了当前这个类里,做了一个关联关系,那么有什么用呢?我们继续往后面看。

  1. asInterface()
public static com.wf.testaidl.PersonAidl asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
         return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.wf.testaidl.PersonAidl))) {
        return ((com.wf.testaidl.PersonAidl) iin);
    }
    return new com.wf.testaidl.PersonAidl.Stub.Proxy(obj);
}

这个方法主要就干了一个事儿,那就是当发起IPC请求时把我们从Binder驱动拿到的这个IBinder描述转换成一个具体的AIDL接口对象,后面才能调用相应的接口方法。这里面首先调用了一个queryLocalInterface,我们来看看为什么要先调用这个方法呢?

public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

这里可以看出它是拿这个descriptor去判断是不是调用的自己,是就直接返回自己(明白了上面那个attachInterface的作用了吧)。这又是为什么呢?这里就有一个要注意的点了,上面我们给出的那个200万的图是不是有一个本地服务,那么为什么会有一个本地服务呢?这里我就要反问一个问题了:是不是我们需要访问的服务一定是在不同的进程呢?答案是不是的,我们也有可能会访问自己的服务,对吧!好了,那么我想你应该明白了这里为什么会有一个queryLocalInterface了吧。
好了,我们接着往下看,如果我们访问的不是自己就需要拿这个IBinder描述去创建一个远程的AIDL接口对象。

  1. onTransact()
@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_addPerson: {
            data.enforceInterface(DESCRIPTOR);
            com.wf.testaidl.bean.Person _arg0;
            if ((0 != data.readInt())) {
                _arg0 = com.wf.testaidl.bean.Person.CREATOR.createFromParcel(data);
            } else {
                _arg0 = null;
            }
            this.addPerson(_arg0);
            reply.writeNoException();
            return true;
        }
        case TRANSACTION_getPersonList: {
            data.enforceInterface(DESCRIPTOR);
            java.util.List<com.wf.testaidl.bean.Person> _result = this.getPersonList();
            reply.writeNoException();
            reply.writeTypedList(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
 }

这个方法就是来处理我们的IPC请求的,根据不同的code值最终会调到它的实现类的对应的接口方法,然后将处理后的结果写入replay返回给客户端。这里面的代码都很简单,就不一一分析了,相信大家自己也能看明白。

  1. class Proxy这个我们单独放到下一节来讲。
  2. 此刻我想你一定有一个问题:服务不是要注册到ServiceManager嘛,上面没有哪里讲到注册了啊?其实它在我们上面讲的构造函数里面就已经完成了注册,无参构造函数的初始化首先会调用父类的无参构造函数,那我们就来看看它的父类Binder的无参构造函数:
public Binder() {
    init();
    ...
}

private native final void init();

从上面我们可以看出它调用了一个native方法,就是在这里进行注册的。那么你又会问了:这里没有传任何数据啊?其实这里native方法它会隐式的把this传过去,那我们之定义的DESCRIPTOR就注册过去了。
到这里我们的Stub服务端就基本讲完了。



Proxy
上面我们剖析了一下Stub的源码,理解到了Stub其实是作为一个服务端注册到ServiceManager来响应我们的请求的。接下来我们就来看我么是怎么样发起请求的呢?首先我们来看一下这个Proxy是什么。

private static class Proxy implements com.wf.testaidl.PersonAidl {
    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;
    }

    @Override
    public void addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException {...}

    @Override
    public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException { ...}
}

Proxy作为一个Stub的静态内部类主要用来发起跨进程的请求,那一个进程为什么既有Stub又有Proxy呢?因为一个进程它既可以是客户端也可以是服务端。好了,接下来我们一步步分析Proxy的源码,看看它是怎么工作的。

  1. 构造函数
Proxy(android.os.IBinder remote) {
    mRemote = remote;
}

初始化的时候传入一需要访问的进程的IBinder描述,也就是我们发起IPC请求的时候从ServiceManager里面查询寻到的IBinder,这样我们才知道是要去访问哪个进程。

  1. 实现接口方法addPerson()和getPersonList()
@Override
public void addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        if ((person != null)) {
            _data.writeInt(1);
            person.writeToParcel(_data, 0);
        } else {
            _data.writeInt(0);
        }
        mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
        _reply.readException();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

@Override
public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.util.List<com.wf.testaidl.bean.Person> _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
        _reply.readException();
        _result = _reply.createTypedArrayList(com.wf.testaidl.bean.Person.CREATOR);
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

首先初始化两个Parcel对象,都是通过Parcel.obtain()去Parcel池(队列)里面拿的(节约内存)。然后将我们要访问的进程(DESCRIPTOR)和传递的参数写入Parcel。接下来就调用IBinder引用的transact()去连接 Binder驱动。

mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);

这里有一个很有意思也是最简单的做法,它把要访问的方法定义成了一个int类型的常量TRANSACTION_addPerson和TRANSACTION_getPersonList,这两个常量是定义在它外部类stub里面的,这样IBinder引用拿到后就知道是要去调用哪个方法了。
接下来我们看看这个transact()做了些什么事情?

public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
            int flags) throws RemoteException {
    if (false) Log.v("Binder", "Transact: " + code + " to " + this);

    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

当Binder驱动拿到我们的请求和数据后,它就会去连接我们要访问的服务端进程,然后通知它回调onTransact()方法。到这里我们的整个请求就算完成了。


小结

  1. 一个进程既可以是服务端Stub,也可以是客户端Proxy。
  2. 一个IPC请求发起时,首先会调用Proxy去连接Binder驱动,然后Binder驱动再去连接Stub
  3. 要实现跨进程通信,两个进程必须要又相同的AIDL接口


三、调用流程分析
  1. ServiceManager的注册和获取
    上面我们讲到了Binder进程间通信时所有的Server都时由ServiceManager来管理的,那么你可能会问了ServiceManager也时一个独立进程,那么它是什么时候启动?又是什么时候注册的呢?
    系统启动的时候回去执行\system\core\rootdir\init.rc,在这里面就启动了ServiceManager,启动之后会去执行service_manager.c,那这里面干了什么呢?
int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;

    bs = binder_open(128*1024);

    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }

    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}

打开bind驱动,并且分配128K大小,紧接着把自己注册为Service 大管家,然后就开启loop来读取Binder驱动的消息处理各种服务请求。

  1. 客户端发起IPC请求
private PersonAidl mPersonAidl;
//绑定服务
Intent intent = new Intent(this, AidlService.class);
bindService(intent, new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mPersonAidl = PersonAidl.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mPersonAidl = null;
    }
}, BIND_AUTO_CREATE);
//请求接口
mPersonAidl.addPerson(person);
List<Person> personList = mPersonAidl.getPersonList();

上面我们是通过显示意图来绑定的,因为我们是写在一起然后配置在不同进程的,当然你也可以用隐式意图来绑定服务,那么接下来我们就来看看bindService做了什么?

@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
    return mBase.bindService(service, conn, flags);
}

那我们就来看看mBase.bindService又是干了什么?

public abstract boolean bindService(@RequiresPermission Intent service,
            @NonNull ServiceConnection conn, @BindServiceFlags int flags);

我们可以看到它调用的是一个抽象方法,那么我们就自然要去找到它的实现类,最终我们找到了ContextImpl.java里面。

@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags){
    warnIfCallingFromSystemProcess();
    return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser());
}

那么我再来看看 bindServiceCommon又干了些啥?

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user){
    ...
    int res = ActivityManager.getService().bindService(...)
    ...
}

好,到这里就开始跨进程访问了,通过ActivityManagerService去获取远程服务,最终会在ActivityThread.java的handleBindService()方法里回调远程服务的onBind方法返回一个IBinder引用,这样我们就能通过这个IBinder引用去调用相应的接口来进程跨进程访问。后面具体的底层源码这里就不贴出来了,感兴趣的话可以自己去翻阅源码。

  1. 自定义服务端注册
public class AidlService extends Service {

    private List<Person> mPersons = new ArrayList<>();
    private IBinder mIBinder = new PersonAidl.Stub() {
        @Override
        public void addPerson(Person person) throws RemoteException {
            mPersons.add(person);
        }

        @Override
        public List<Person> getPersonList() throws RemoteException {
            return mPersons;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mIBinder;
    }
}

上面的代码就是我们自定义的一个服务,它里面实现了我们的PersonAidl.Stub,上一节我们分析的Stub源码里的onTransact最终会回调到这里我们具体的实现。并且这里可以看到它里面实现了一个onBind方法来返回这个IBinder引用。

<service
    android:name=".service.AidlService"
    android:exported="true"
    android:enabled="true"
    android:process=":aidl"/>

上面这段代码就是把我们的服务声明到aidl进程,这里没有什么要说的,都是很标准的写法。


总结

好了,到这里呢,我们这次的Binder进程间通信机制的探索就告一段落了。主要掌握以下几点:

  1. Linux内存空间分为用户空间和内核空间,用户空间不能共享,内核空间可以共享;
  2. Binder机制的四个重要角色:Client、Server、ServiceManager、Binder驱动;
  3. Binder机制采用的是C/S架构,Binder驱动是他们之间的桥梁亦是整个架构的核心。
  4. Android中Binder主要的表现形式就是AIDL,重点理解Stub和Proxy模式。

以上就是我个人对Binder机制的一个理解,如果有说错的或者理解不到位的地方都可以指出来,我们一起学习改正,谢谢!
最后我们给出举例的源码地址:https://github.com/WangFion/TestAidl

Android
Web note ad 1