您的当前位置:首页正文

binder机制笔记

来源:花图问答

binder四个主要模块

BinderClient(调用者) 

 BinderServer(被调用者) 

ServerManager(中介,存储BinderServer的信息(名字-实体) BinderClient通过它来查询到BinderServer进行调用) 

BinderDrivder(处于内核空间,负责跨进程通信的数据传递(好像是这样),BinderServer会生成一个实体同时会绑定一个名字,封装成数据包发送给BinderDrivder,BinderDriver会在内核空间对BinderServer进行实体创建(具体不清楚),然后将数据传递给ServerManager,ServerManager取出BinderServer的名字和引用保存到一张数据表中)

跨进程通信的时候通过代理类来进行访问操作。

代理类A和被代理类B(extends Binder) 实现相同的Interface,B中定义静态方法asInterface用于生成A,A持有者B的引用,并将A对外提供,A调用方法时,会对方法的参数进行Parcel邮包化。

Parcel对象通过jni接口传递到Binder的C++空间,最终传递到BinderDrivder。BinderDrivder会让A所在进程休眠,并且将传过来的Parcel数据从A进程映射到B。此时B的ontransact方法会被调用。根据参数code判断具体执行哪个方法,然后取出数据,根据数据进行方法调用,然后将返回值写入到replay(Parcel)中再反向的传递,从binder驱动传递到C++中间层,再通过JNI传递到java层。将A所在进程唤醒,然后A接受到replay,从replay中读取返回值,返回给调用A的对象。

网上关于Binder的文章有很多,不少大牛高手围绕Binder施展了十八般武艺,想要将它解剖干净展示给大家看,文章的水平都很高,但可惜的是,都有点对开发者不友好,为什么?因为它们都是down-to-top,而不是top-to-down。作为应用开发者,我们肯定更喜欢top-to-down的讲解。本文则是一种尝试。衷心感谢Weishu的这篇文章。声明:本文属作者原创,欢迎随意转载。但请不要修改文章内容并在开头注明本人she账号milter和原文链接。

确定问题

本文围绕着这样一个问题展开:如何从进程A传两个整数给进程B,进程B把两个数相加后返回结果给进程A。

Binder机制框架

Android给我们提供了跨进程通信的一揽子解决方案。下面我们从总体上看一看这个方案是怎样设计的:进程A通过bindService方法去绑定在进程B中注册的一个service,系统收到进程A的bindService请求后,会调用进程B中相应service的onBind方法,该方法返回一个特殊对象,系统会接收到这个特殊对象,然后为这个特殊对象生成一个代理对象,再将这个代理对象返回给进程A,进程A在ServiceConnection回调的onServiceConnected方法中接收该代理对象,依靠这个代理对象的帮助,就可以解决我们的问题啦。

分步骤分析 第一步

为进程B实现一个特殊的对象,就是前面提到的service的onBind方法要返回的对象。这个对象有两个特性:

一个是具有完成特定任务的能力(在我们的问题中,就是将两个整数相加并返回结果的能力) 一个是被跨进程传输的能力。

什么样的对象具有这样的能力呢?答案是Binder类的对象。下面我们分析一下Binder是怎样拥有这两个能力的。Binder中有如下关键方法:   

public class Binder implement IBinder{ 

void attachInterface(IInterface owner, String descriptor)

IInterface queryLocalInterface(Stringdescriptor) //从IBinder中继承而来 

boolean onTransact(int code, Parcel data, Parcel reply, int flags)//暂时不用管,后面会讲。 

final class BinderProxy implements IBinder { ......//Binder的一个内部类,暂时不用管,后面会讲。 } }

Binder具有被跨进程传输的能力是因为它实现了IBinder接口。系统会为每个实现了该接口的对象提供跨进程传输,这是系统给我们的一个很大的福利。Binder具有的完成特定任务的能力是通过它的attachInterface方法获得的,我们可以简单理解为该方法会将(descriptor,owner)作为(key,value)对存入Binder对象中的一个Map对象中,Binder对象可通过attachInterface方法持有一个IInterface对象(即owner)的引用,并依靠它获得完成特定任务的能力。queryLocalInterface方法可以认为是根据key值(即参数 descriptor)查找相应的IInterface对象。onTransact方法暂时不用管,后面会讲到。

好的,现在我们来实现IInterface和Binder对象,概略代码如下:

public class Plus implement IInterface { 

public int add(int a,int b){//定制我们自己的相加方法 return a+b; } 

IInterface owner = new Plus();

public class Stub extends Binder { 

@Override boolean onTransact(int code, Parcel data, Parcel reply, int flags){ ......//这里我们覆写了onTransact方法,暂时不用管,后面会讲解。 } 

Binder binder = new Stub();

binder.attachIInterface(owner,"PLUS TWO INT");

第二步

好了,现在我们有了这个特殊的对象binder,可以在进程B的service中的onBind方法将它返回了,即return binder ;下面就是见证奇迹的时候。系统会首先收到这个binder对象,然后,它会生成一个BinderProxy(就是前面提到的Binder 的内部类)类的对象,姑且称之为binderproxy,然后将该对象返回给进程A,现在进程A终于在onServiceConnected方法中接收到了binderproxy对象(心情有木有小激动?)。为了下面讲解方便,再次贴出Binder类的概要信息。

public class Binder implement IBinder{ 

void attachInterface(IInterface owner, String descriptor) 

IInterface queryLocalInterface(Stringdescriptor) //从IBinder中继承而来 

boolean onTransact(int code, Parcel data, Parcel reply, int flags)//暂时不用管,后面会讲。 final class BinderProxy implements IBinder { 

IInterface queryLocalInterface(Stringdescriptor) { return null ;//注意这行代码!! //下面会讲到。这行代码只是示例,不是源代码。 } ...... } }

此时的进程A以为收到的是binder对象,它兴奋了,它迫不及待地要通过queryLocalInterface方法获取这个binder的owner对象,利用该对象的加法功能进行加法计算。可结果呢?首先,binderproxy.queryLocalInterface("PLUS TWO INT")调用是合法的,因为queryLocalInterface方法是IBinder中的方法,而BinderProxy和Binder都实现了IBinder接口。但是,binderproxy对象显然没有owner对象,因为它根本就没有attachInterface方法(这是Binder才有滴)。所以,可想而知,进程A的binderproxy.queryLocalInterface("PLUS TWO INT")调用返回的将是一个null(参见上面的示例代码)。

第三步

进程A出离愤怒了,我要的是binder,我要的是它里面的owner来帮我完成加法运算,进程B竟然给我一个冒牌货binderproxy(显然,它冤枉了进程B,都是系统惹得祸)。正在进程A气得头顶冒烟时,binderproxy对象说话了:“别生气进程A,我虽然只是binder对象的代理,但是,我也不是吃素的,你把你的数据(两个int)和你想进行的操作(owner.add)通过我的transact方法(这是在IBinder接口中定义的方法)交给我,我可以替你向binder对象请求你需要的功能,等binder对象把结果给我时,我再把结果交给你不就行了?”于是,进程A通过binderproxy对象的transact方法,提交了请求。代码概略如下: 

android.os.Parcel data = android.os.Parcel.obtain(); 

android.os.Parcel reply = android.os.Parcel.obtain(); 

int _result=data.writeInterfaceToken("PLUS TWO INT"); 

data.writeInt(a); 

data.writeInt(b); 

binderproxy.transact(1, data, reply, 0);//为简单起见,最后一个0暂时不管它简单解释一下上面代码。data是用来写进程A的数据的(即整数 a和b),reply是准备用来接收结果的。transact方法中的第一个参数是整数1,它是进程A与进程B的一个约定,1就代表想让进程B对进程A传入的数据执行加法操作。这个约定也可以定义在 Stub类中,如下所示:public static final int ADD = 1;此时,我们可以将binderproxy.transact(1, data, reply, 0);中的1替换为Stub.ADD。Stub.ADD其实可以是任何整数值的,我们选择1纯属为了简单。

第四步

binderproxy.transact调用发生后,会引起系统的注意,系统意识到binderproxy想找它的真身binder对象执行一个操作了(看!系统其实一直存着binder和binderproxy的对应关系呢!)。于是系统将这个请求中的数据转发给binder对象,binder对象将会在onTransact中收到binderproxy传来的数据(Stub.ADD,data,reply,0),于是它从data中取出进程A传来的数据,又根据Stub.ADD确定进程A想让它执行加法操作,于是它就执行了加法操作,并把结果写回reply。代码概略如下:

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 Stub.ADD: { data.enforceInterface("PLUS TWO INT"); 

int _arg0; _arg0 = data.readInt(); 

int _arg1; _arg1 = data.readInt(); 

int _result = this.queryLocalIInterface("PLUS TWO INT") .add(_arg0, _arg1); reply.writeNoException(); 

reply.writeInt(_result); 

return true; }} 

return super.onTransact(code, data, reply, flags); }

简单解释一下以上代码。我们知道进程A写数据时写入了一个InterfaceToken,就是这行代码data.writeInterfaceToken("PLUS TWO INT");这个意思是说,让进程B在自己的binder对象中利用PLUS TWO INT调用queryLocalIInterface方法查找相应的IInterface对象,进程A要执行的操作就在该对象中,至此,我们很容易理解Stub.ADD就代表了owner中的add方法。这是一个二级查找过程,即通过PLUS TWO INT确定要owner来执行功能,通过Stub.ADD确定要执行owner中的add方法。

第五步

进程B把结果写入reply后,进程A就可以从reply读取结果了。代码概略如下:... binderproxy.transact(Stub.ADD, data, reply, 0); 

reply.readException();

 _result = reply.readInt();

动态代理

参考链接:

参考书籍: