Framework
Android系统分成三层。最上层是application应用层,第二层是Framework层,第三层是native层、Android中的应用层和系统服务层不在同一个进程,系统服务在单独的进程中。 2、Android中不同应用属于不同的进程中 Android应用和系统services运行在不同进程中是为了安全,稳定,以及内存管理的原因,但是应用和系统服务需要通信和分享数据。
优点 安全性:每个进程都单独运行的,可以保证应用层对系统层的隔离 稳定性:如果某个进程崩溃了不会导致其他进程崩溃。内存分配:如果某个进程已经不需要了可以从内存中移除,并且回收相应的内存
程序中的多进程
虚拟机分配给各个进程的运行内存是有限制的,LMK也会优先回收对系统资源的占用多的进程。
多进程的好处
- 突破进程内存限制,如图库占用内存过多,因为操作系统分配内存的时候是按照进程来分配的;
- 功能稳定性:独立的通信进程保持长连接的稳定性;
- 规避系统内存泄漏: 独立的WebView进程阻隔内存泄露导致的问题:
- 隔离风险:对于不稳定的功能放入独立进程,避免导致主进程崩溃
比如腾讯的包里边,就会有很多进程同时在运行。
Binder的出现
出身
George Hoffman当时任Be公司的工程师,他启动了一个名为OpenBinder
的项目,在Be公司被ParmSource公司收购后,OpenBinder由Dinnie Hackborn继续开发,后来成为管理ParmOS6 CobaltOS的进程的基础。在Hackborn加入谷歌后,他在OpenBinder的基础上开发出了Android Binder(以下简称Binder),用来完成Androi的进程通信。
为什么要学习binder
- startActivity的时候,会获取AMS服务,调用AMS服务的startActivity方法
- startActivity传递的对象为什么需要序列化
- bindService为什么回调的是一个lbinder对象
- 多进程应用,各个进程之间如何通信AIDL的使用
Binder是什么?
Binder就是Android中的血管。在Android中我们所使用的Activity,Service等组件都需要和AMS(system server)通信,这种跨进程的通信都是通过Binder完成。
- 机制:Binder是一种进程间通信机制;
- 驱动:Binder是一个虚拟物理设备驱动;
- 应用层:Binder是一个能发起通信Java类:
- Framework/Native:Binder连接了Client、Server、Service Manager和Binder驱动程序,形成一套C/s的通信架构。
为什么选择Binder?
首先我们需要了解安卓中有哪些跨进程通信的方式:
- 共享内存:WS与SurfaceFlinger通信的时候,如果有大块的数据需要交给SF去渲染的时候,就会用到。安卓的Ashmem机制也是利用的共享内存。较大的 bitmap 直接通过 Intent 传递容易抛异常是因为 Intent 启动组件时,系统禁掉了文件描述符 fd 机制 , bitmap 无法利用共享内存,只能拷贝到 Binder 映射的缓冲区,导致缓冲区超限, 触发异常; 而通过 putBinder 的方式,避免了 Intent 禁用描述符的影响,bitmap 写 parcel 时的 allowFds 默认是 true , 可以利用共享内存,所以能高效传输图片。
- 管道:子进程与父进程通信。
- 消息队列。
- Socket:AMS与zygote通信。
- binder。 这些通信方式都需要经过内核空间。 Server与Client通过Binder通信的时候,会为每一个Client的通信渠道创建一个线程。这也就是Binder线程池的由来。
共享内存
- 优点:速度快,性能最好;
- 缺点:没有同步控制(两个进程同时改变内存,存在互斥访问问题),容易访问紊乱,信息安全无保障。
管道
也存在于内核空间,管道分为命名管道,和无名管道。这两者都需要一个介质来作为管道,而我们往往会用一种特殊的文件来做为管道文件。两个进程通信的时候,会通过写段 写入数据,然后再读段 中读取数据。数据需要经过两次复制,一次是从进程A到管道,另一次是从管道到进程B,反之亦然。管道的读写分为阻塞和非阻塞的。为了提升管道的读写速度,一般的,也会有缓冲区,缓冲区一般也会有大小的限制。如果传输的数据一旦超过了缓冲的大小,或者在阻塞模式下没有安排好管道的读写。都会出现阻塞情况。另外管道上边传递的数据是无格式的字节数字节流。这就要求管道的写入方和读出方约定好数据的格式。
消息队列
消息队列是存在在内核中的一个链表。允许多个进程同时读写消息。发送方与接收方要同时约好消息体的数据类型以及大小。
Socket
由网络通信演变而来,典型的CS模式。需要两次拷贝。Client->内核->Server。
Binder
采用的CS结构。binder通信得益于mmap,内存映射。主要实现方式是将读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,从而减少了从读缓冲区到用户缓冲区的一次CPU拷贝。 Binder在数据传输的过程中需要一个UID,我们可以通过控制这个来实现用户权限管理。Binder的通信数据大小是1M-8k,有这个上限是因为启动组件时,系统禁掉了文件描述符 fd 机制 , bitmap 无法利用共享内存,只能拷贝到 Binder 映射的缓冲区,导致缓冲区超限, 触发异常; 而通过 putBinder 的方式,避免了 Intent 禁用描述符的影响,bitmap 写 parcel 时的 allowFds 默认是 true , 可以利用共享内存,所以能高效传输图片。
进程通信方式对比
Binder通信的基本模型
内存划分
内存被操作系统划分成两块:用户空间和内核空间,用户空间是用户程序代码运行的地方,内核空间是内核代码运行的地方。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。
binder是如何管理的
上图中,ioctl就是系统调用。Client端要与服务端通信,首先必须要拿到Servier端Binder。一般情况下,Server端的Binder会先启动。然后将自己的Binder注册到ServiceManager里边,ServiceManager里边维持了一个表,这个表里边包含了Server的binder。Client直接通过ServiceManager获取到对应的Binder。然后利用这个Binder进行跨进程通信。例如AMS,就是自己把自己注册到SM中的。不过有个问题,ServiceManager也是快进程的通信,我们通过ServiceManager去获取AMS的时候,怎么拿到ServiceManager的Binder对象呢?这是因为ServiceManager的底层handle节点固定了是0,就像DNS服务器一样,他的IP固定为8.8.8.8。 我们在拿到Binder驱动之后,然后再利用Binder驱动来完成真正的跨进程通信。
Binder是什么时候创建的?
为什么Binder需要在App启动时第一时间创建。这是因为进程启动之后会启动Activity等组件,需要跨进程通信。
跨进程通信模型
binder驱动属于Server端,在内核态。我们平常所写的代码是java代码,那么如果通过我们的java代码调用到内核态的驱动呢?这个就涉及到java native的通信,因此我们就产生了AIDL,这个AIDL可以生成java文件,以及C++文件,方便我们利用Binder驱动。
下边就是Android 快进程通信模型
stub
就是一个服务端的代理,使用了统一代码进行实现,是服务端能够提供哪些服务的一个存根 减少了用户的封装过程。 proxy
服务端能够提供哪些服务的一个代理,只是这个是给客户端用的,代理的是Binder驱动。因为Client需要通过这个代理来访问Service,而不能直接通过Stub去访问,
当外部需要调用的时候,将stub类交给对应的调用者,这是因为跨进程是不能直接访问真实的Service对象的,只能访问代理对象。这里的这个new ILeoAidl.Stub()实际上就相当于下图的Binder。 之后调用onBind函数就能够真正的拿到服务端的代理对象了。
所以外部要调用我们的代码,就必须要要使用这个stub的继承类来实现,因为只有这个实现类实现了跨进程通信的基础代码编写。
然后客户端要使用的时候,需要bindService,这里我们需要通过Action和package去确定唯一的服务器的binder对象。
这个connection就是与服务端建立连接。内部的两个回调就是与服务器建立状态的回调,这里的service其实就是服务端的Binder对象。这个IBinder只是一个接口,还需要再后边转变成具体的对应的对象才能完成请求。所以这也就是为什么我们需要把服务器的aidl文件包括路径都要复制到client端。
IMyAidlInterface.Stub.asInterface(service)
这个代码完成了Server端的代理转变成服务端的一个代理的流程。但是最后的请求还是由代理类发出的。
一个服务注册的一个完整流程
附
在Android29之后,Android aidl支持生成C++,命令是(参考): blog.csdn.net/yinminsumen...