目录
前言
主要对Android中的跨进程通信进行总结下,这篇先梳理下里面的涉及的一些概念
一 多进程
1.进程与线程
- 进程:系统中正在运行的一个应用程序,某个程序一旦运行就是一个进程,是资源分配的最小单位;
- 线程:程序执行的最小单位,包含在进程中,一个进程可以包含多个线程。
2.Android应用中的多进程
(1)dalivk虚拟机
Android系统的底层任务管理以及驱动都是基于Linux系统。一个Android系统其实就是一个Linux系统,通过adb shell进入连接的手机,就可以看到Linux系统的文件系统。
像在运行一个Java程序,我们知道Linux系统会启动一个Java虚拟机来运行该Java程序,而Android系统是一个特殊的Linux系统,当启动一个APP,系统会为该APP分配一个Linux进程,而该进程中就会有一个dalivk虚拟机(又称为DVM)实例来运行该APP,所以dalivk虚拟机就是用来运行APP程序。
不同的APP运行在不同的进程中,对应着不同的dalivk虚拟机,就对应着不同的地址空间。反过来在一个应用内,如果新开一个进程,那么由于系统在新开进程的时候会分配独立的dalivk虚拟机,那么对于该APP内的不同进程的实例是互相独立,互不影响。
综上,
- 1)每个进程有独立的dalivk虚拟机,对应着单独的内存空间;
- 2)一个应用可以有多个进程,就有多个dalivk虚拟机,对应多个内存空间;
- 3)一个进程可以被多个应用访问,多个应用可以共享该进程
(2)Linux系统组成
在Linux系统中,虚拟内存空间(我理解的就是运行软件程序的空间,是对物理空间的映射)只有4G。最高的1GB(对应虚拟地址0xC0000000到0xFFFFFFFF)被成为内核空间,而较低的3GB(对应虚拟地址0x00000000到0xBFFFFFFF)被成为用户空间。内核空间是Linux内核运行空间,用户空间是应用程序的运行空间。如图所示:
- 内核空间:可以访问受保护的内存空间,有访问底层硬件设备的所有权限;
- 用户空间:上层应用程序和Native层的运行空间,用户空间没法直接访问内核空间,需要系统调用才可以访问内核空间。
不同进程之间,用户空间是不共享,但是内核空间是可以共享。
(3)定义多进程
默认情况下,启动一个APP,仅仅启动了一个进程,该进程名为包名,那如何定义多进程呢?Android提供了一种方式,就是在AndroidManifest文件中可以通过“android:process”来指定进程:
- 1)不指定process:默认的进程,进程名为包名;
- 2)指定process,但以":"开头:该进程为当前APP的私有进程,不允许其他APP访问
- 3)指定process,但以小写字母开头的字符串:该进程为全局进程 ,其他应用可设置相同的shareUID来共享该进程
遗留问题:需要看下这个怎么共享进程
(4)为什么要引入多进程
为什么一个Android应用要引入多进程?多进程有哪些应用场景呢?通常在下面两种情况下需要引入多进程:
- 由于Android系统会限制每个应用的最大内存,所以如果一个应用需要更多可用的内存时,就需要引入多进程,让某些模块运行在另外的进程中,获取更多的内存;
- 由于不同的应用运行在不同的进程中,但是如果两个不同的应用之间需要进行数据通信
(5)跨进程通信
既然在Android中引入了多进程,而对于进程的用户空间不共享,那么多进程之间怎么通信呢?这种多进程通信又称为IPC(Inter Process Communication)。对于IPC,并不是Android系统特有的,在Linux系统中就存在的跨进程通信,在Linux系统中常见的IPC方式有:
- 1)管道Pipe:在内存中创建一个共享文件,利用共享文件传递信息。该共享文件并不是文件系统,只存在于内存中;只能在一个方向上流动
- 2)信号Signal:异步通信。信号在用户空间和内核空间之间交互,内核可利用信号来通知用户空间的进程发生哪些系统事件。不适用于信号交换,适用于过程中断控制;
- 3)信号量Semaphore:控制多个进程对共享资源的访问。主要是进程间以及同一进程不同线程之间的同步手段;
- 4)消息队列 Message Queue:存放在内存中并由消息对了标识符标识,允许一个或多个进程对它进行读写消息。信息会复制两次,不适用于频繁或信息量大的通信
- 5)共享内存Shared Memory:直接读写内核的一块内存空间。不需要进行数据拷贝
- 6)套接字Socket:不同机器之间进程间通信。
而在Android中,场景的IPC方式有:
- 1)Bundle:实现了Parcelable接口,常用于Activity、Service、BroadcastReceive之间的通信
- 2)文件共享:常用于无并发,交换实时性不高的数据
- 3)Messenger:低并发的一对多即时通信。串行的方式处理Client发来的消息,只能传输数据,不能方法调用(RPC)
- 4)ContentProvider:存储和获取数据,不同程序之间共享数据。一对多的数据共享
- 5)AIDL
- 6)Socket:网络数据交换
除去Socket,其他的都是基于Binder机制实现的。
遗留问题:Messenger这个后面要去研究下
(6)多进程带来的问题
前面在(2)进程结构中也提到了用户空间是不共享的,并且每个进程都是对应单独的系统堆栈区、静态区等,那么多进程也引入了一些问题:
- 每个进程都是保持自己单独的静态成员变量和单例;
- 每个进程都是单独的进程锁;
- SharedPreferences可靠性下降,不支持并发写;
- 对于单个APP的多进程,就会创建多个Application,每个进程都会拥有自己的Application对象。
题外话:
最后的这个信息让我想起之前看web开发相关的一些信息:Tomcat也是运行在Linux系统中,在部署web应用的时候,可以把一个web应用部署到多个Tomcat服务器上,那么每个Tomcat服务器都会给该web应用分配一个ServletContext
二 Binder
1.Android系统架构
在去了解Binder之前,先看下Binder在Android系统架构中的位置,如图:
该Android的架构从下往上大体功能如下:
- 1)Linux Kernel:Linux内核。像Audio、Camera等硬件对应的软件层的驱动程序、线程和内存管理、Binder等;
- 2)HAL:硬件抽象层。向Java层输出手机硬件的功能。在Linux Kernel中都是直接通过C来读写手机硬件,而HAL是对这些操作的进一层封装,让C++可以更方便的来使用这些功能。
- 3)Android Runtime:Android应用程序运行的环境,用来将运行程序语言转化成机器可以识别的机器语言。连接了Framework和Library,包括core lib和虚拟机(DVM)。
- 4)C++:在Android系统中核心组件和服务都是通过C和C++编写,那么在Framework在调用这些功能的时候,就需要通过JNI来访问这些内容;
- 5)Java API Framework:给APP调用系统核心组件和服务的API;
- 6)System APP:系统应用
Binder其实就是工作在Linux Kernel,其实Binder更通俗点来讲就是访问内存空间的驱动程序。
2.APP进程的创建过程
在Linux系统中,所有的进程都是有init进程(Linux内核系统启动的第一个用户级进程)的子孙进程,所有的进程都是有init进程直接或间接fork出来的。
在Android系统中,当然启动的第一个进程仍然为init进程,在init进程启动Zygote进程。该Zygote进程来创建Android系统后续的其他进程。那么Android的其他进程的启动流程如下:
- 1)Android系统启动,启动init进程;
- 2)init进程创建Zygote进程;
- 3)Zygote进程在创建的时候,需要完成如下工作:“创建一个Server端的Socket,等待AMS的请求创建APP的进程-->-->预加载类和资源-->-->fork出SystemServer进程”;
- 4)SystemServer进程在创建的时候,需要完成如下工作:“启动Binder线程池-->-->创建SystemServiceManager(对系统服务进行创建、启动、生命周期管理)-->-->启动各种关键系统服务(如AMS、WMS等)”;
- 5)AMS启动Launcher;
- 6)当点击APP的icon,AMS就会通过Socket通道,通知Zygote创建一个APP进程。
详细内容参见Android 跨进程通信-(三)Binder机制之Client
3.Binder
(1)什么是Binder
所谓的Binder,是一种通信机制,目前是实现IPC,整个架构采用的是C/S架构。其中有四个组件:Client、Server、Binder驱动以及ServiceManager:
- 1)ServerManager:可以理解为路由器。管理各种Service的注册与查询。ServerManager会将这些字符串的Binder转化成该Binder的引用。
- 2)Client:可以理解为客户端。如果访问Server进程,需要先从ServerMananger中查询Service,得到访问的Server的代理对象;
- 3)Server:可以理解为服务器。提供服务,需要将Service注册到ServerManager中。是一个Binder对象,接收从Binder驱动发送过来的消息,收到消息后,就会执行onTransact();
- 4)Binder驱动:可以理解为访问内存空间的驱动程序。负责管理数据的接收缓存。持有每个Server进程在内核空间的Binder实体,并且给Client进程提供Binder实体的引用。主要作用通过内存映射传递进程间数据和线程控制。
- 在Binder驱动中包括binder的binder_open()、binder_mmap()、binder_ioctl()、管理内核缓冲区等操作。
Client、Server、ServiceManager运行在用户空间,分别都是单独的进程;而Binder驱动运行在内核空间。
1.对几个常见的字符串的概念区分:
- 在Natvie层调用binder_open()/binder_mmap()/binder_ioctl(),最后都会调用到Binder驱动程序的对应方法,完成了用户空间访问和操作内核空间;
- copy_from_user()、copy_to_user()是Linux提供的用户空间和内核空间传递数据的方法:从用户空间传递到内核空间使用的是copy_from_user(),而将内核空间传递给用户空间使用的是copy_to_user()
2.Binder在进行进程通信之前的准备工作:
- 需要创建ServerManager进程、Client进程以及Server进程:具体创建过程参Android 跨进程通信-(二)Binder机制之ServiceManager、Android 跨进程通信-(三)Binder机制之Client、Android 跨进程通信-(四)Binder机制之Server具体介绍。
(2)Binder的通信机制
Client、Server、ServiceManager工作为用户空间,Binder驱动工作在内核空间。所以其中Client、Server在ServiceManager中查询和注册服务是不可以之间通信的,而是需要通过Binder驱动(open和ioctl文件操作)来进行间接通信,其流程大体如下:
第一步:注册
- 1)Server进程向Binder驱动发送注册服务的请求;
- 2)Binder驱动将该请求发送给ServiceManager进程;
- 3)ServiceManager进程进行添加该Server进程的引用;
上述过程ServiceManager进程拥有了Server进程的信息。
第二步:查询
- 4)Client进程向Binder驱动发送服务的名字,以获取服务;
- 5)Binder驱动将该请求发送给ServiceManager进程,ServiceManager进程根据服务的名字找到对应的Server的服务信息;
- 6)ServiceManager进程通过Binder驱动将服务信息返回给Client进程;
此时该Client进程已经与Server进程建立了连接,这个过程就是Binder中的IPC。
第三步:使用
Binder在传递数据的时候只需要拷贝一次数据,性能上仅次于共享内存。
在创建APP进程的时候,既会在在用户空间创建一块虚拟内存,同时也会在内核空间创建一块虚拟内存,并且申请一块物理内存同时映射到这两块虚拟内存中;同样Service进程在创建的时候也会有相同的操作。那么通过内存映射实现了将数据拷贝到Client进程的用户空间和Server进程的用户空间只需要通过一次拷贝完成数据传递。具体的这个实现过程见Android 跨进程通信-(五)Binder机制之一次拷贝的原理
补充一点:
- 1.消息队列和管道需要将数据先从发送方缓存区拷贝到内核开辟的缓冲区,然后再从内核缓冲区拷贝到接收方的缓冲区,需要两次拷贝;
- 2.共享内存无需拷贝,但控制复杂;
- 3.socket传输效率低,通常用于跨网络的进程间通信
在内核空间,Binder驱动传递数据是通过内存来传递数据,所以Binder会创建一块内存用来传递数据(这个具体的内存是怎么开辟的可参见Android 跨进程通信-(五)Binder机制之一次拷贝的原理)。大体的传递流程如下:
- 7)Client进程将要传递的数据放入(通过copy_from_user)到Client进程在内核空间对应的虚拟内存区;(当前进程被挂起,处于中断等待状态)
- 8)从Client进程在内核空间对应的虚拟内存拷贝到Binder驱动的内存,从由于Binder驱动创建的内存和Server进程在内核空间对应的虚拟内存是同一内存地址,所以相当于直接拷贝到,通知Server进程读取数据(之前Server进程启动之后,就会进入到中断等待状态);
- 9)Server进程接到通知之后,就会从线程池中取出对应的线程,解析数据,并将最后的处理结果写入到Server进程在内核空间对应的虚拟内存中;
- 10)将Server进程在内核空间对应的虚拟内存中的数据拷贝到Binder驱动的内存中,同样由于内存映射的原因,相当于直接拷贝到了Client进程在内核空间对应的虚拟内存中,并通知Client进程读取数据;
- 11)Client进程接到通知之后,Client进程读取在内核空间对应的虚拟内存中的数据。(当前线程被唤起)
此时就是Client进程就实现了和Server进程之间传递数据,这个过程就是Binder中的RPC(Remote ProcessCall)远程过程调用。
遗留问题:Binder中的线程池的概念
(3)Android中的API
Binder驱动是运行在内核空间,使用的C来实现的,而最终需要实现的用户空间的数据共享,准确的说是应用层的不同用户空间的数据共享,而应用层的相关实现都是用Java实现的,那么从代码实现来看Binder需要包括以下几部分:
- 1)在Framework层对上层应用的封装,也有对Native的封装Client、Server、ServiceManager的封装;
- 2)在Native对Client、Server、ServiceManager的封装;
- 3)在Kernel对Binder驱动的封装。
具体每一部分的介绍,可参见在后面几篇总结。
三 总结
其实这一篇是在总结了Android 跨进程通信-(二)Binder机制之ServiceManager、Android 跨进程通信-(三)Binder机制之Client、Android 跨进程通信-(四)Binder机制之Server、Android 跨进程通信-(五)Binder机制之一次拷贝的原理之后才逐渐理出来。简单的在总结下:
- 1.系统中运行的每个APP都可以认为是一个进程,进程是系统资源分配的最小单位;而一个进程是有线程组成的。
- 2.系统每运行一个APP,都会分配一个进程,在进程中就会分配独立的dalivk虚拟机来运行APP。dalivk虚拟机是专门用来运行Android程序;
- 3.当然一个APP也可以分配多个进程,就会对应多个dalivk实例;
- 4.Linux系统把4G的虚拟内存空间的0G~3G作为用户空间,高3G~4G作为内核空间:APP运行在用户空间;
- 5.不同APP所在的用户空间是无法进行通信的,必须通过系统调用到内核才可以完成不同APP之间的通信;
- 6.内核空间有访问最底层硬件设备的所有权限;内核空间是共享的;
- 7.由于APP的单个进程有内存限制,所以就可以通过在APP中增加进程的方式来增大APP可用的内存空间,所以需要引入多进程;
- 8.处在不同进程中的两个APP之间数据通信的时候,所以就需要引入多进程之间的通信IPC;
- 9.在Android中常用的IPC有AIDL、ContentProvider、Bundle等;
- 10.如果一个APP有多个进程,那么这些进程之间都保持自己单独的静态成员变量和单例,并且拥有单独的Application实例;
- 11.在Android中通过在内核空间的Binder驱动来实现IPC;
- 12.在Android的Binder机制中有四个重要角色:ServiceManager、Server、Client以及Binder驱动:其中前三个运行在用户空间,Binder驱动运行在内核空间;
- 13.在创建ServiceManager、Server、Client进程的时候,都会在对应的用户空间创建虚拟内存,同样也会在内核空间创建虚拟内存,同时会有一块物理空间同时映射到这两块虚拟内存;
后面在研究完Android 跨进程通信-(五)Binder机制之一次拷贝的原理这个之后,在完善这篇内容。