Android 框架学习(3)—— Binder框架

一、概述

        Linux中的IPC方式有管道、信号量、共享内存、消息队列、Socket。Android系统是基于Linux系统的,理论上应该使用Linux内置的IPC方式,但是Android不继承Linux中原有的IPC方式,而选择使用Binder,Binder机制不属于Linux。为什么Android选择使用Binder:

  • 从通信方式上说,我们希望得到的是一种Client-Server的通信方式,但在Linux的五种IPC机制中,只有Socket支持这种通信方式。虽然我们可以通过在另外四种方式的基础上架设一些协议来实现Client-Server通信,但这样增加了系统的复杂性,在手机这种条件复杂、资源稀缺的环境下,也难以保证可靠性。
  • 从传输性能上说,Socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信;消息队列和管道采用存储-转发方式,即数据先从发送方拷贝到内存开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程;共享内存虽然无需拷贝,但控制复杂,难以使用;而Binder只需要拷贝一次。
  • 从安全性上说,Android作为一个开放式的平台,应用程序的来源广泛,因此确保智能终端的安全是非常重要的。Linux传统的IPC没有任何安全措施,完全依赖上层协议来确保,具体有以下两点表现:第一,传统IPC的接收方无法获得对方可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份,使用传统IPC时只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用;第二,传统IPC的访问接入点是开放的,无法建立私有通信,只要知道这些接入点的程序都可以和对端建立连接,这样无法阻止恶意程序通过猜测接收方的地址获得连接。

        基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式、传输性能和安全性的要求,这就是Binder。Binder是一种基于Client-Server通信模式的通信方式,传输过程只需要一次拷贝,可以为发送方添加UID/PID身份,支持实名Binder和匿名Binder,安全性高。

二、什么是Binder?

        Binder从不同角度上的定义:

  • 直观来说,Binder是Android中的一个类,它实现了IBinder接口。
  • 从IPC角度来说,Binder是Android中的一种跨进程通信方式,该通信方式在Linux中没有。
  • 从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。
  • 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据了。

        Binder机制具体有两层含义:

  • Binder是一种跨进程通信(IPC,Inter-Process Communication)的手段。
  • Binder是一种远程过程调用(RPC,Remote Procedure Call)的手段。

        在Android中,有很多Service都是通过Binder来通信的,比如MediaService名下的众多Service:

  • AudioFlinger音频核心服务。
  • AudioPolicyService:音频策略相关的重要服务。
  • MediaPlayerService:多媒体系统中的重要服务。
  • CarmeraService:有关摄像/照相的重要服务。

三、整体架构

在这里插入图片描述
        Binder的实现分为这几层,按照大的框架理解是:

  • Framework层
    • Java层
    • JNI层
    • Native/C++层
  • Linux驱动层C语言

        其中Linux驱动层位于Linux内核中,它提供了最底层的数据传递,对象标示,线程管理,通过调用过程控制等功能。驱动层其实是Binder机制的核心。
        Framework层以Linux驱动层为基础,提供了应用开发的基础设施。Framework层既包含了C++部分的实现,也包含了Java基础部分的实现。为了能将C++ 的实现复用到Java端,中间通过JNI进行衔接。
        开发者可以在Framework之上利用Binder提供的机制来进行具体的业务逻辑开发。其实不仅仅是第三方开发者,Android系统中本身也包含很多系统服务都是基于Binder框架开发的。其中Binder框架是典型的C/S架构。所以在后面中, 我们把服务的请求方称为Client,服务的实现方称之Server。Clinet对于Server的请求会经由Binder驱动框架由上至下传递到内核的Binder驱动中,请求中包含了Client将要调用的命令和参数。请求到了Binder驱动以后,在确定了服务的提供方之后,再从下至上将请求传递给具体的服务。如下图所示:
在这里插入图片描述

四、通信机制

(1)Binder在C/S中的流程
        Android内部采用C/S架构,而Binder通信也是采用C/S架构。
在这里插入图片描述
        具体流程如下:

  • 相应的Service需要注册服务。Server作为很多Service的拥有者,当它想向Client提供服务时,得先去注册Service。
  • ServiceManager(以后缩写成SM)那儿注册自己的服务。Server可以向SM注册一个或者多个服务。
  • Client申请服务。Client作为Service的使用者,当他想使用服务时,得向SM申请自己所需要的服务。Client可以申请一个或者多个服务。
  • 当Client申请服务成功后,Client就可以使用服务了。

        SM一方面管理Server所提供的服务,同时又响应Client的请求并为之分配响应的服务。扮演角色相当于月老,两边牵线。这种通信方式的好处是:一方面,service和Client请求便于管理,另一方面在应用程序开发时,只需要为Client建立到Server的连接即可,这样只需要花很少的时间成本去实现Server的相应功能。那么Binder与这个通信有什么关系?其实三者的通信方式就是Binder机制(比如Server向SM注册服务,使用Binder通信;Client申请请求也是Binder通信。)
        请注意:这里的ServiceManager是指Nativie层的ServiceManager(C++),并非是Framework层的ServiceManager(Java)。ServiceManager是整个Binder通信机制的大管家,是Android进程间通信机制的守护进程。
(2)通信整体框架
        后面会不断提及两个概念,一个是Server,还有一个是Service,一个Server下面可能有很多Service,但是一个Servcie也只能隶属于一个Server。
从内核和用户空间的角度来看
在这里插入图片描述
        可以发现:

  • Client和Server是存在于用户空间。
  • Client和Server通信实现是由Binder驱动在内核的实现。
  • SM作为守护进程,处理客户端请求,管理所有服务。

从Android的层级的角度
在这里插入图片描述
        图中Client/Server/ServiceManager之间的相互通信都是基于Binder机制。图中Clinet/Server/ServiceManager之间交互都是虚线表示,是由于他们彼此之间不直接交互,都是通过Binder驱动进行交互,从而实现IPC通信方式。其中Binder驱动位于内核空间,Client/Server/ServiceManager可以看做是Android平台的基础架构。而Client和Server是Android的应用层,开发人员只需要自定义实现client、Server端,借助Android的基本平台架构就可以直接进行IPC通信。
从Binder的架构角度来看
在这里插入图片描述
        Binder IPC 属于 C/S 结构,Client 部分是用户代码,用户代码最终会调用 Binder Driver 的 transact 接口,Binder Driver 会调用 Server,这里的 Server 与 service 不同,可以理解为 Service 中 onBind 返回的 Binder 对象,请注意区分下。

  • Client端:用户需要实现的代码,如 AIDL 自动生成的接口类。
  • Binder Driver:在内核层实现的 Driver。
  • Server端:这个 Server 就是 Service 中 onBind 返回的 IBinder 对象。

        需要注意的是,上面绿色的色块部分都是属于用户需要实现的部分,而蓝色部分是系统去实现了。也就是说 Binder Driver 这块并不需要知道,Server 中会开启一个线程池去处理客户端调用。为什么要用线程池而不是一个单线程队列呢?试想一下,如果用单线程队列,则会有任务积压,多个客户端同时调用一个服务的时候就会有来不及响应的情况发生,这是绝对不允许的。
        对于调用 Binder Driver 中的 transact 接口,客户端可以手动调用,也可以通过 AIDL 的方式生成的代理类来调用,服务端可以继承 Binder 对象,也可以继承 AIDL 生成的接口类的 Stub 对象。
        这里 Server 的实现是线程池的方式,而不是单线程队列的方式,区别在于,单线程队列的话,Server 的代码是线程安全的,线程池的话,Server 的代码则不是线程安全的,需要开发者自己做好多线程同步。

扫描二维码关注公众号,回复: 11137442 查看本文章

五、工作流程

        假设:客户端的程序Client运行在进程A中,服务端的程序Server运行在进程B中。
        由于进程的隔离性,Client不能读写Server中的内容,但内核可以,而Binder驱动就是运行在内核态,因此Binder驱动帮我们进行请求的中转。
        有了Binder驱动,Client和Server之间就可以打交道了,但是为了实现功能的单一性,我们为Client和Server分别设置一个代理:Client的代理Proxy和Server的代理Stub。这样,由进程A中的Proxy和进程B中的Stub通过Binder驱动进行数据交流,Server和Client直接调用Stub和Proxy的接口返回数据即可。
        此时,Client直接调用Proxy这个聚合了Binder的类,我们可以使用一系列的Manager来屏蔽掉Binder的实现细节,Client直接调用Manager中的方法获取数据,这样做的好处是Client不需要知道Binder具体怎么工作。
        最后还有一个问题,就是Client想要获得的服务多种多样,那么它是怎么获取Proxy或Manager的呢?答案是通过Service Manager进程来获取的。Service Manager总是第一个启动的服务,其他服务端进程启动后,可以在Service Manager中注册,这样Client就可以通过Service Manager来获取服务器的服务列表,进而选择具体调用的服务器进程方法。
        上面的叙述总结为如下图所示的工作流程图:
在这里插入图片描述

原创文章 45 获赞 51 访问量 5443

猜你喜欢

转载自blog.csdn.net/hezhanran/article/details/105678339