进程 Process | 线程 Thread | |
相同 | 将系统比作工厂,进程就是一个个车间,线程就是车间里的一条条流水线。APP在启动的时候就开启了一个进程,默认情况下一个APP的所有组件都运行在同一进程和线程中(进程是包名,线程是主线程)。 | |
不同 | 系统资源调度的基本单位,程序的一个运行实例(可看作工厂中的车间)。 | CPU调度的基本单位(可看作车间中的一条条流水线)。 |
进程间相互独立,有各自的资源和地址空间。 | 同一进程的各线程间资源地址空间共享。 | |
一个进程崩溃在保护模式下不会影响其它进程运行,多进程可以保证其它模块的正常运行。 | 一个线程崩溃整个进程都挂掉。 | |
进程切换消耗大效率低。 | 频繁切换线程更好,并发且共享变量只能用线程。 | |
在 MainiFest 中对 Application 或四大组件设置 android:process 属性可以为其分配独立的进程,同时设置以组件属性为准。属性值(进程名称)可以随意定义,":xxx"以冒号开头表示进程私有其他APP无法访问,进程名称变成"包名:xxx"。 | 线程不是越多越好,最好是CPU个数+1。 |
默认情况下一个APP的所有组件都运行在同一进程中,在 MainiFest 中对 Application 或四大组件设置 android:process 属性可以为其分配独立的进程,同时设置以组件属性为准。属性值(进程名称)可以随意定义,":xxx"以冒号开头表示进程私有其他APP无法访问,进程名称变成"包名:xxx"。
好处 | 打破内存限制:虚拟机给每一个进程分配的内存是有限制的,多个进程可以获得更多内存空间(图库等大型文件存放)。 |
稳定性:主进程杀死,子进程仍然可以运行(推送)。 | |
隔离风险:将可能出现问题的功能放在一个独立进程中,即使崩溃导致进程结束,其它进程也能正常运行。 | |
坏处 | 内存不共享。 |
Application会多次创建。 |
通常会将一些初始化代码放在 Application 中,而每当运行指定了进程的组件时都会新建一个 Application 对象,只需要在 Application 类中对当前进程加以判断即可。
override fun onCreate() {
super.onCreate()
if (isMainProcess()) init() //确保在主进程下才初始化(多进程会导致Application重复创建)
}
/**
* 当前进程是否为主进程
*/
private fun isMainProcess(): Boolean {
val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
val runningProcesses = activityManager.runningAppProcesses
if (runningProcesses != null) {
for (process in runningProcesses) {
if (packageName.equals(process.processName)) {
return true
}
}
}
return false
}
一个进程的优先级是可以变化的,当可用内存低的时候重要性低的进程先被杀死,前台进程 > 可视进程 > 服务进程 > 后台进程 > 空进程。
前台进程为数不多,只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应,这就需要依靠系统的调度。
用户正在与该进程进行交互。 | 1.正在交互的 Activity,即处于 onResume() 状态。 |
2.当某个 Service 绑定正在交互的 Activity。 | |
3.Service被主动调用为前台,即调用了 startForeground() 函数。 | |
4.Service正在执行 onCreate()、onStart()、onDestroy()。 | |
5.BroadcastReceiver 正在执行 onReceive()。 |
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
不在交互但仍影响可见的界面。 | 1.Activity无法交互但仍然可见,即处于 onPause() 状态。例如启动了一个dialog被挡在后面。 |
2.绑定到可见(前台)Activity 的 Service。 | |
3.Service正在执行 stratForeground()。 | |
4.托管系统用于用户知道的特定功能的服务,例如动态壁纸、输入法服务等。 |
通过 startService() 方法启动的service,它不属于上面提到的2种更高重要性,所在的进程虽然对用户不是直接可见,但执行了用户非常关注的任务(播放mp3、网络数据上传下载)。只要前台进程和可见进程有足够的内存,系统不会回收他们。
不可见的activity(即处于 onStop() 状态)。这些进程对用户体验没有直接的影响,通常统中会有很多不可见的后台进程在运行,它们按照 LRU (least recently used) 规则,以便内存不足的时候被第一时间回收。如果一个 activity 正确的执行了它的生命周期,关闭这个进程对于用户体验没有太大的影响。
不含任何活动组件的进程。目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
Android中IPC方式 |
特点 | 使用场景 |
Bundle(Intent) | 只能传输实现了序列化或者一些Android支持的特殊对象。 | 适合用于四大组件之间的进程交互。 |
File | 不能做到进程间的即时通信,并且不适合用于高并发的场景。 | 适合用于SharedPreference以及IO操作。 |
ContentProvider | 可以访问较多的数据,支持一对多的高并发访问,因为ContentProvider已经自动做好了关于并发的机制。 | 适合用于一对多的数据共享并且需要对数据进行频繁的CRUD操作。 |
Socket | 通过网络传输字节流,支持一对多的实时通信,但是实现起来比较复杂。 | 适合用于网络数据共享。 |
Messager | 底层原理是AIDL,进行跨进程通信时请求队列是同步进行的,无法并发执行,并且传输的数据只能支持Bundle类型。 | 单线程。 |
AIDL | 功能强大,使用Binder机制,支持一对多的高并发实时通信,但是需要处理好线程同步。 | 跨APP、多线程。 |
Android中涉及到多进程间的通信底层都是依赖于 Binder 机制。
Android下的Binder | Linux下传统IPC通信 | |||
Binder | Socket | 消息队列/管道Pipe | 共享内存 | |
性能 | 拷贝一次 | 拷贝两次 | 无需拷贝 | |
特点 | 基于C/S(Client-Server)架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立。 | 传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。 | 采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。 | 控制复杂难以使用。 |
安全性 | Android为每个安装好的APP分配UID,进程的 UID 是鉴别进程身份的重要标志,同时支持实名(系统服务)和匿名(自己创建的服务)。 | 没有任何安全措施,完全依赖上层协议来确保。接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。只能由用户在数据包中填入 UID/PID,但这样不可靠容易被恶意程序利用。访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。 |
- 发送方进程将数据存放在内存缓存区中,通过系统调用进入内核态。
- 内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。
- 接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区。
- 内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保证安全用户进程不能直接操作内核,从逻辑上将虚拟空间划分为内核空间(Kernel Space)和用户空间(User Space)。
用户空间中进程与进程之间内存不共享无法交换数据(彼此独立拥有各自的资源空间),想要访问对方数据需要通过内核空间,因此需要采用进程间通信机制(Inter-process Communication),
- 当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。
- 当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
- 系统调用主要通过两个函数实现:copy_from_user()(将数据从用户空间拷贝到内核空间)、copy_to_user()(将数据从内核空间拷贝到用户空间)。
如上所述,进程间通信需要内核空间做支持,传统通信机制都是内核的一部分,Binder 并不是 Linux 内核的一部分,利用 Linux 的动态内核可加载模块机制 LKM(Loadable Kernel Module),Android通过添加一个内核模块(Binder驱动)运行在内核空间,作为用户进程之间通信的桥梁。
通过 mmap() 内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。具体是将用户空间的一块内存区域映射到内核空间,建立映射关系后,两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知,实现了进程间通信。
- Binder 驱动在内核空间创建一个数据接收缓存区。
- 内核程序在内核空间分配内存,开辟一块内核缓存区,建立内核缓存区和数据接收缓存区之间的映射关系,以及数据接收缓存区和接收进程用户空间地址的映射关系。
- 发送进程通过系统调用 copy_from_user() 将数据拷贝到内核空间的内核缓存区,由于内核缓存区和数据接收缓存区存在内存映射,而数据接收缓存区又和接收进程的用户空间存在内存映射,因此相当于把数据发送到了接收进程。
Binder通信采用C/S架构(Client-Server),从组件视角来说包含Client、Server、ServiceManager、Binder驱动。
- 从IPC角度看:Binder是一种机制。
- 从Server进程角度看:Binder指的是 Server 中的 BInder 实体。
- 从Client进程角度看:Binder指的是 对 Binder 实体对象的远程代理。
- 从传输过程角度看:Binder是一个可以跨进程传输的对象,BinderDriver会对这个过程处理来完成代理对象和本地对象之间的转换。
用户空间 | 开发者实现 | Client(类比客户端,使用服务的进程。) | 通过系统调用open()、mmap()、ioctl()来访问设备文件/dev/binder与binder驱动交互来间接实现进程间通信。 | |
Server(类比服务器,提供服务的进程。) | ||||
系统提供 | ServiceManager(类比DNS域名服务器,将Client端中字符串形式的Binder名字转化为Server端中Binder实体的引用。) | |||
内核空间 | BinderDriver(类比路由器,负责进程之间Binder通信的底层支持。) | - |
Android接口定义语言(Android Interface Definition Language),可用来定义客户端与服务均认可的编程接口,以便二者进程间通信。
IBinder | 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。 |
IInterface | 是一个接口,代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口) |
Binder | Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。 |
Stub |
视野个抽象类,AIDL 的时候编译器 会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。 |
Module右键→New→AIDL→AIDL File(如果不可选,在build.gradle的buildFeatures节点添加aidl true)。
在创建的接口中定义抽象方法。
interface IDemoAidlInterface {
//自己的业务
void setName(String name);
String getName();
}
Rebuild一下项目,就会生成对应的Java类。Android目录在 java(generated) 里,Program目录在 build/generated/aidl_source_output_dir 里。
别忘了在 AndroidManifest 中注册Service。
class DemoService : Service() {
override fun onBind(intent: Intent): IBinder {
return IDemoImpl() //返回实现类对象
}
//实现接口(注意是.Stub)
class IDemoImpl : IDemoAidlInterface.Stub() {
private var name: String = "成功了!"
override fun setName(name: String?) {
this.name = name.orEmpty()
}
override fun getName(): String = name
}
}
Moudle右键→New→Filder→Aidl Folder 新建文件夹,然后创建和 Server 端相同的包名将 Server 端的 .aidl 文件复制粘贴过去,最后Rebuild一下。
除了之前创建的 Service 需要注册,还要添加查询包的权限,包名是 Server 端的。
val conn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
//拿到对象就可以调用各种方法了
val demoBinder = IDemoAidlInterface.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {}
}
//开启服务
val intent = Intent()
intent.component = ComponentName("com.jomuiphys.service.service", "com.jomuiphys.service.service.DemoService")
context.bindService(intent, conn, AppCompatActivity.BIND_AUTO_CREATE)
如果App还存在缓存进程,这个时候启动App,应用Application的onCreate方法会执行吗?
点击返回键后系统执行 MainActivity 的 onDestory(),这个时候APP进程为缓存进程,下次启动APP会发现 Application 的 onCreate() 并不会执行,MainActivity的生命周期都会正常执行,这是因为从缓存进程启动APP,系统已经缓存了很多信息,很多数据并不会被销毁,onCreate() 中初始化的那些内容还在,方便用户下次快速启动。利用这一特性,我们的App首次启动速度一般为5-600ms,退出App后存在缓存进程的情况下,每次启动的速度一般为2-300ms,算是某种程度上提升了App的启动时间。