我眼中的Binder

先来看系统服务:

    首先从系统刚刚启动说起,init进程会启动一个叫ServiceManager的进程,该进程启动之后会做三件事:(1)通过open打开设备文件/dev/binder,将该文件中的内容通过mmap映射到本进程空间中;(2)通过IO控制命令BINDER_SET_CONTEXT_MGR将当前进程注册到Binder驱动中,Binder驱动便会为他在内核空间创建一个称为binder_context_mgr_node的节点出来,这个节点也就是ServiceManager在内核空间的Binder实体了,并且将他的句柄设置为0,他的创建是在系统启动的时候,这个节点在Binder驱动中是唯一的,所以也就造成了ServiceManager区别于其他Server了,但他仍然是运行在用户空间的;(3)ServiceManager启动之后就会无限循环等待Server和Client之间的进程间通信请求了;接着Zygote进程会孵化出一个子进程SystemService,我们的大部分系统服务比如ActivityManagerService、WindowManagerService、MediaPlayService等都是该进程内的一个线程,这些服务会通过调用ServiceManager.addService方法添加到ServiceManager中的服务列表svclist中,这样我们的系统服务就已经都注册到了ServiceManager里面了;

    上面讲了ServiceManager,他是随着系统的启动而创建的,它不同于普通的Server,甚至可以理解为是普通Server的Server,系统会将系统所要提供的服务注册到它的服务列表里面,服务列表中存储的便是服务名字+服务在Binder驱动中的句柄,这里的句柄可以理解为同一进程内部的引用,只不过现在是跨进程通信换了个名字而已,如果我们的应用想要用到某个系统服务的话,可以通过传入服务名字调用ServiceManager.getService得到该服务对应的Binder对象的方式进行获取,接着通过这个Binder对象进行相应操作即可,具体这就是ContentProvider为我们封装的内容了;

    Binder是通过客户端/服务端模式实现的,要想明白Binder的实现原理,我们需要明白几个概念之间的关系:Server/Client/ServiceManager/Binder驱动,他们四个是Binder架构的核心,尤其是ServiceManager和Binder驱动,因为我们的应用程序安装到系统上的时候,都会被分配一个唯一的UID用户ID,他是运行在独立进程中的,Linux基于用户安全的考虑是不允许我们直接访问系统上服务的,因为他们分属于不用的进程,进程间的数据是不能共享的,我们要想访问这些系统服务就涉及到了进程间通信了,那么势必就需要内核空间的参与了,所以用到了Binder驱动,它有点类似于桥的作用;那么平常我们是怎么做到访问系统服务的呢?比如媒体资源,是通过ContentProvider,他的底层实现就是Binder,只不过被封装了而已;

    我们可以通过一个例子来说明他们四者之间的关系,假如你想问你同学借点钱,那么你现在就是Client端,你同学就是Server端,但是你两是异地的,不能直接见面,怎么办呢,打电话呗,首先你先从通讯录里面找到你同学的电话,这里的通讯录就是ServiceManager了,然后打过去借就好了,那么电信运营商就是Binder驱动了,没有他你两根本联系不起来;

    讲完了所有涉及到的概念之后,就该看看是怎么通信的了,也就是ServiceManager.addService和ServiceManager.getService到底做了些什么事了?

    首先来说说ServiceManager.addService,也就是我们的Server是怎么和Binder驱动通信的:

    ServiceManager.addService(String name, IBinder service):传入的内容是Server的名字和该Server在Binder驱动中对应的对象;

    (1):Server首先将自己作为对象,并且附上一个句柄为0的值(用于访问ServiceManager),将这些内容封装成一个数据包,open有关Binder的设备文件/dev/binder,将封装好的该数据包发送给Binder驱动;

    (2):Binder驱动在收到这个数据包之后,发现里面存在一个Server对象,首先会在Binder驱动自己里面新建该Server对应的Binder实体,并赋予一个大于0的句柄,同时会将该句柄也加入到数据包中,接着将该数据包发送到句柄为0对应的对象上面,也就是ServiceManager上面了;

    (3):ServiceManager收到转发给自己的数据包之后,会查看其服务列表svlist中是否已经存在当前Server名字的服务,不存在的话,会将当前服务+当前服务对应于Binder驱动中的句柄加入到列表中;

    这样,系统服务就注册到ServiceManager中了;

    接下来便是ServiceManager.getService部分了,也就是Client是怎么和Binder驱动进行通信,返回对应请求的Binder对象,也就是该Server在Binder驱动中的句柄的:

    ServiceManager.getService(String name):传入的参数是将要请求的服务的名字

    (1):Client首先会将要获取的服务的名字以及一个句柄为0的值(为了访问ServiceManager)封装成一个数据包,open有关Binder的设备文件/dev/binder,将该数据包发送给Binder驱动;

    (2):Binder驱动在收到数据包之后发现里面有句柄为0的信息,就将该数据包转发给了ServiceManager来进行处理了;

    (3):ServiceManager在收到数据包之后根据服务的名字查看自己的服务列表svclist,找到之后会将其对应的在Binder驱动中的句柄信息也封装成一个数据包;

    (4):该数据包也会通过Binder驱动被发送给Client端;

    (5):Client端在收到数据包之后,就得到了自己所请求的服务在Binder驱动中的句柄,他会利用这个句柄信息在自己本地创建一个远程Server的代理,以后Client发消息都是发给这个代理的,随后的通信便变成了代理通过Binder驱动与真正的Server进行交互了,以此完成跨进程间的通信;

    这样系统服务是怎么注册到ServiceManager里面以及我们怎么获得这些服务对应于Binder驱动的句柄也就是Binder对象的过程讲解就已经结束了,接下来便是我们自定义服务是怎么通过Binder进行进程间通信的呢?

 自定义服务:

    这里就要用到Android为我们自定义进程间通信所提供的AIDL文件了,没有这个文件,你完全也可以实现跨进程通信,但是序列化,反序列化数据的封装都将需要你自己来实现,有了AIDL之后这个过程会显的比较简单,你并不需要关心序列化反序列化的顺序问题,只需要将Server进程想要提供给Client进程访问的方法定义在一个.aidl文件中即可,我们在此将他命名为Ixxx.aidl,那么系统将会为该AIDL文件自动生成对应的Ixxx.Java文件;

    简单说说Ixxx.java的类结构,将是下面这样的伪代码形式:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
public interface Ixxx extends IInterface
{
public static abstarct class Stub extends Binder implements Ixxx
{
public static Ixxx asInterface(Binder binder){}
public Binder asBinder(){}
public boolean onTransact(int code,Parcel data,Parcel reply,int flags){}
private static class Proxy implements Ixxx
{
public Binder asBinder(){}
//Ixxx接口中的一些实现方法,当然这些实现并不是真正的逻辑实现,而只是通过transcat进行的一些进程切换操作
}
}
}
可以看到Ixxx.java中存在一个Stub静态抽象类和Prxoy静态类,最关键的方法就是Stub类中的asInterface了,他会根据我们传入的Binder对象来判断是跨进程通信还是进程内部通信,如果是进程内部通信,该方法返回的将是Ixxx.Stub对象;如果是跨进程
通信返回的将是Ixxx.Stub.Proxy对象,这个代理对象中将的方法会通过调用transact方法来进行内核态的切换;

    下面我以文字的方式简述下我们自定义服务实现跨进程通信的原理:

    (1):首先,我们需要创建一个AIDL文件,将其命名为Ixxx.aidl,里面定义了服务端进程想要提供给客户端进程的方法列表,系统会为我们生成一个Ixxx.java文件;

    (2):我们的自定义服务端主要是通过service来实现的,在service里面实现具体提供给客户端的方法的操作代码,并且通过onBind方法返回此服务端对应的Binder对象,而后我们的客户端通过bindService绑定服务端的时候就可以获得这个服务端的Binder对象了;

    (3):在客户端获得Binder对象之后会调用Ixxx.Stub的asInterface方法将Binder对象传入,获得Ixxx对象,在这里就将跨进程和通进程通信分开了,如果是跨进程通信的话asInterface返回的是Ixxx.Stub.Proxy代理对象,那么以后客户端调用服务端的方法实际上是首先调用的Ixxx.Stub.Proxy代理对象里面对应于服务端的方法,这个代理对象的方法会通过transact陷入内核态来进行实际上的进程间通信调用服务端的onTransact方法,在onTransact方法中会根据标志调用不同的服务端方法;如果是同进程通信的话,asInterface返回的是Ixxx.Stub对象,则直接调用服务端方法,没有必要陷入内核态来执行了;

    这也便是我们自定义服务实现进程间通信的简单过程了;

    在使用AIDL实现Binder通信的过程中,我们应该注意一点的就是,AIDL中不管是服务端方法还是客户端方法都是运行在各自的Binder线程池中的,如果我们想要更新UI的话,需要用到Handler进行切换操作;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 JavaScript 编写的简单俄罗斯方块游戏(附源代码) 项目:使用 JavaScript 编写的简单俄罗斯方块游戏(附源代码) 该游戏是一个使用 HTML5 和 JavaScript 开发的简单项目。这款游戏允许玩家策略性地旋转下落的方块以清除关卡。要在此游戏中得分,您必须通过操纵方块来清除线条,使它们填满水平行。当方块掉落时,您必须 根据需要移动 和旋转它们,使它们均匀地排列在底部。 游戏制作 该游戏仅使用 HTML、CSS 和 JavaScript。谈到这款游戏的功能,这款游戏的 PC 控制也很简单。首先,您必须按空格键才能开始游戏。您可以使用箭头键来更改下落方块的位置。您可以在侧栏看到形成的分数和行。 该游戏包含大量的 javascript,用于对游戏的某些部分进行验证。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox, 以获得更好、更优化的游戏体验。要玩游戏,首先,单击 index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值