Android系统启动流程
加载Bootloader–>初始化内核————>启动init进程————>init进程fork出Zygote进程–>zygote进程fork出Systemserver进程
引导层:
手机开机后,引导芯片启动,引导芯片开始从固化在ROM里的预设代码执行,加载引导程序到到RAM,BootLoader检查RAM,初始化硬件参数等功能;
主要任务是将操作系统加载到内存中并建立好操作系统运行的环境,然后将控制权转移到操作系统内核。
例子:nexus 5x 按住音量调低键,然后按住电源键。进入bootloader
内核层:
Kernel层主要加载一些硬件设备驱动,初始化进程管理等操作。在Kernel中首先启动swapper进程(pid=0),用于初始化进程管理,内容管理,加载Driver等操作,再启动kthread进程(pid=2),这些1inux系统的内核进程,Kthread是所有内核教程的鼻祖。
Native层:
启动初始化进程管理等操作会启动init进程,这些在Native层中,init进程是所有进程的鼻祖,解析执行init.rc,到app_process孵化zygote进程。Zygote进程会预加载大量的Java类和资源,以提高应用程序的启动速度。当用户启动新的应用程序时,Zygote进程会复制自身并生成新的虚拟机实例,然后加载对应应用程序的代码并执行。
java框架层:
zygote进程会加载虚拟机,启动System_Server进程。System Server是整个Android系统中非常重要的一个进程,负责启动并管理多种系统服务,例如Activity Manager、Window Manager、PackageManager等。在这个阶段,用户才可以看到开始界面,开始使用手机。
应用层
SystemServer阶段会启动Binder线程池,创建SystemServiceManager管理系统服务,之后启动各种系统服务,其中包括Launcher,就是我们的系统桌面。
Zygote进程
Zygote 是 Android 系统中非常重要的一个进程,它是所有应用进程的母体进程,负责创建和管理 Android 应用的运行环境。Zygote 的设计思想是通过进程复制(fork)机制,提高应用程序启动的效率,同时也减少了内存的使用和系统资源的消耗。
Zygote 的启动过程
(1)Zygote 启动
- 当 Android 系统启动时,
init
进程会根据启动脚本(/init.rc
文件)来启动 Zygote 进程。 init
进程会调用 Zygote 可执行文件(通常为/system/bin/app_process
),通过app_process
启动 Zygote 的主方法com.android.internal.os.ZygoteInit.main()
。
(2)加载 Android 系统库和框架类
- Zygote 进程启动后,会首先加载一系列系统库和资源,包括 Android 的核心类库、系统框架等。这些库会被所有应用程序共享。
- 例如,Zygote 会预加载 Android SDK 中的核心类(如
android.app.*
、android.content.*
等),以及一些重要的系统资源,以便所有应用程序都可以快速访问这些类和资源。
(3)创建 Socket 接口
- Zygote 创建一个 Zygote Socket 接口,用于与 System Server 及其他系统组件通信。当应用启动请求到来时,Zygote 通过这个 Socket 接收启动请求并进行进程复制(fork)。
(4)启动 System Server 进程
- 在 Zygote 启动的过程中,Zygote 进程会首先创建一个关键的系统进程 —— System Server。
- System Server 是 Android 系统服务的核心,它包含窗口管理、活动管理、包管理等系统服务。通过
fork
机制,Zygote 生成 System Server 进程后,System Server 初始化并启动各种系统服务,使 Android 系统具备完整的功能。
(5)进入等待状态
- 在完成初始化任务后,Zygote 进入一个等待状态,等待通过 Zygote Socket 接收应用进程启动请求。
- 每当有应用启动请求到来时,Zygote 进程会 fork 一个新的进程来运行该应用。
在native主要作用:
startVM
创建虚拟机-startVM函数
1.配置虚拟机参数:startVM
函数会设置一些虚拟机启动参数,包括堆大小、垃圾回收策略等。根据不同的设备性能和内存情况,Android 系统可以调整这些参数来优化性能。
2.创建虚拟机实例:startVM
会调用 Android 虚拟机(ART 或 Dalvik)的 API 来创建一个新的虚拟机实例(JNIEnv
指针),并将它绑定到当前 Zygote 进程中。
3.初始化 Java 类库:一旦虚拟机实例创建完成,Zygote 会在虚拟机中加载和初始化 Android 的核心 Java 类库。这些类库将为所有应用程序提供公共的运行环境。
startReg
注册JNI函数-startReg
startReg
函数:此函数的任务是将本地(Native)方法绑定到 Java 方法,使得 Native 层代码可以调用 Java 层代码。
1.加载系统库和函数:startReg
会注册 Android 系统中的关键 JNI 函数,这些函数允许 Native 代码调用 Java 层的功能。
2.绑定 JNI 函数:系统中许多核心组件都依赖于 JNI。通过 startReg
函数,Zygote 进程将必要的 JNI 函数注册到虚拟机环境中,使得本地代码能够访问系统服务,例如内存管理、文件系统、网络等资源。
3.与 Java 交互:通过 JNI 函数的注册,Android 系统的 Native 层(C/C++ 编写)和 Java 层可以互相调用,达到良好的协同运行效果。
ZygoteInit.main
通过JNI知道Java层的com.android.internal.os.ZygoteInit类,调用main函数,法入java世界
ZygoteInit.main
方法:这是 Java 层的入口点,用于初始化 Zygote 进程并启动 Android 系统的服务。
- 初始化系统服务:
ZygoteInit.main
方法首先会启动 System Server 进程,这是 Android 系统的核心服务进程。System Server 负责启动和管理系统中几乎所有的核心服务,包括 Activity Manager Service、Package Manager Service、Window Manager Service 等。 - 启动 Zygote Socket:
ZygoteInit.main
方法还会创建一个 Socket 接口,名为 Zygote Socket。这个 Socket 用于接收来自 Activity Manager Service 的新应用启动请求。每当用户启动应用时,Activity Manager 会通过此 Socket 向 Zygote 发送请求,Zygote 再 fork 新进程来启动该应用。 - 等待应用请求:完成 System Server 和 Zygote Socket 的初始化后,
ZygoteInit.main
方法会使 Zygote 进入一个等待状态,持续等待应用启动请求。当收到请求时,Zygote 会根据请求参数来 fork 一个新的应用进程,并加载指定的应用。
Android应用程序启动流程
应用的启动
1 AMS启动Launcher
SystemServer启动了一个更加重要的服务Activity Manager Service (AMS) ,AMS其中很重要的一个作用就是启动Launcher进程。Launcher进程是用户与设备交互的核心入
AMS
发送启动应用程序进程请求:AMS
首先会调用startProcessLocked
方法,最终通过openZygoteSocketIfNeeded
方法与Zygote
的Socket建立连接,并通过zygoteSendArgsAndGetResult
与进程进行通信,发送请求。
2 发送启动应用程序进程请求
当用户点击桌面上的应用图标时,首先发生的事情是 Launcher 应用程序调用 startActivity()
方法来启动目标应用的 MainActivity(或其它指定的 Activity)。
startActivity()
方法调用的实际底层是通过 Binder IPC(进程间通信)来与系统服务进行交互的。
通过Binder,调用system_server进程中AMS服务的startActivity方法,发送启动请求.
3 Zygote进程接收请求并创建应用程序进程
当 Activity Manager Service (AMS) 收到启动请求后,它需要创建一个新的进程来运行目标应用。由于 Android 系统使用 Zygote 进程来fork所有应用进程,AMS 会向 Zygote 发送一个进程创建的请求。
Zygote
进程接收请求并创建应用程序进程:Zygote
通过runSelectLoop
方法接收到AMS
的请求,然后调用runOnce
方法,通过forkAndSpecialize
方法创建应用程序进程。
4应用程序进程的初始化
在 Zygote 进程中创建新的应用进程后,系统会继续执行应用进程中的 ActivityThread
类的 main()
方法。这个方法是 Android 应用进程启动的入口,它负责执行应用进程的初始化操作。
应用程序进程初始化和运行:应用程序进程创建完成后,首先会通过handleParentProc
开始进行初始化工作,启动Binder
线程池,最后通过抛出异常的方式调用MethodAndArgsCaller
来启动应用程序进程,即进入到ActivityThread
的main
方法,创建并启动主线程的消息循环,进入阻塞状态。
5应用程序进程的运行
当 ActivityThread.main()
被调用后,ActivityThread
进程会继续执行以下几个关键步骤:
- **创建
Handler
和Looper
**:- Android 的
Handler
和Looper
构成了一个消息队列机制,它们帮助应用程序处理异步事件。每个应用进程都会有一个主线程,主线程会有一个Looper
,负责从消息队列中读取消息并通过Handler
来处理。 - 在应用启动时,
ActivityThread
会创建一个Handler
对象,它负责处理 UI 事件和系统消息。
- Android 的
- **
bindApplication()
**:bindApplication()
是ActivityThread
在启动时调用的一个方法,用于绑定应用的Application
类。- 在
bindApplication()
中,ActivityThread
会初始化应用的环境,设置Context
,执行Application
类的onCreate()
方法等。这些初始化操作会确保应用在启动后可以正确运行。
**初始化应用并启动 Activity
**:ActivityThread
会初始化应用的 Application
类,并启动目标 Activity
。
Activity的启动
在 Zygote 进程通过 fork()
创建出新的应用进程之后,ActivityThread
类会执行应用进程的初始化工作。ActivityThread
负责启动应用的 Application
类、加载启动的 Activity
等。
初始化—->
在 Android 系统中,**Application
** 类是每个应用的入口点之一,它会在整个应用程序的生命周期内持续存在,负责全局初始化和一些资源的配置。**Application
** 的初始化流程通常是在 Activity
的生命周期之前执行的。当应用启动时,ActivityThread
会首先创建一个 Application
类的实例。这个 Application
类是在整个应用生命周期中唯一存在的对象。
Application
类是整个应用的入口点,它在应用启动时就需要进行初始化操作,而 MainActivity
只是用户界面的入口点,Activity
的生命周期方法是在 Application
初始化之后才会开始执行的
Application的构造函数—->
当 ActivityThread
创建 Application
实例时,系统会调用 Application
类的构造函数。此时,Application
实例已经被创建出来,但它尚未完成完全的初始化。
注意:Application
的构造函数在整个应用的生命周期中只会调用一次。
1 | public class MyApplication extends Application { |
Application.attachBaseContext()——>
attachBaseContext()
是 Application
类中的一个重要方法,它会在 onCreate()
之前被调用。这个方法主要是为了将 Context
绑定到 Application
上。实际上,Context
是 Android 中非常重要的一个概念,它提供了应用环境的一些功能,如访问资源、启动活动、访问文件系统等。
在调用 attachBaseContext()
时,系统会将 Context
对象传递给 Application
,并确保 Application
在执行任何任务之前已经具有了适当的上下文。
1 |
|
attachBaseContext()
方法常常用于执行一些与上下文(Context)相关的初始化操作,像是配置一些全局的设置,或者注入一些依赖等。
Application.onCreate()函数–>
onCreate()
方法是 Application
类的核心方法之一,它在应用的整个生命周期内只会被调用一次。它会在 attachBaseContext()
调用之后执行,用于执行更复杂的初始化操作,例如全局配置、SDK 初始化、资源加载等。
1 |
|
最后才会进入MainActivity中的attachBaseContext函数、onCreate函数等等
MainActivity
会首先调用 attachBaseContext()
方法,它的作用和 Application
类中的 attachBaseContext()
类似:为 Activity
提供合适的 Context
。但与 Application
的 attachBaseContext()
不同,Activity
的 attachBaseContext()
是在创建 Activity
之前调用的,因此可以用来处理一些与上下文相关的初始化工作。
在完成 attachBaseContext()
调用后,系统会调用 MainActivity
的 onCreate()
方法。此时,MainActivity
已经完全准备好,可以执行 UI 的初始化和其他业务逻辑。
类加载流程
类的加载分为 1装载(Load),2.链接(Link),3.初始化(Intialize)
在 Android(以及 Java)系统中,类加载 是一个分层的过程,主要涉及到 Native
层和 Java
层之间的交互。
双亲委派机制:
加载.class文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍
如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级classLoader如果没有加载过,则尝试加载,加载失败,逐级向下交还调用者加载
(1)先检查自己是否已经加载过class文件,用findLoadedClass方法,如果已经加载了直接返回
(2)如果自己没有加载过,存在父类,则委派父类去加载,用parent.loadClass(name,false)方法,此时会向上传递,然后去父加载器中循环第1步,一直到顶级ClassLoader
(3)如果父类没有加载,则尝试本级classLoader加载,如果加载失败了就会向下传递,交给调用方式实现.class文件的加载
1 | //路径:/libcore/ojluni/src/main/java/java/lang/ClassLoader.java |
作用:
1 | (1) 防止同一个.class文件重复加载 |
DefineClass->
DefineClass
是加载类的第一步,通常是在 Native 层调用的。
DefineClass 的主要作用是将类的字节码(从
.dex
文件中读取)转换成一个可以被虚拟机识别的对象格式。在 Android 中,
.dex
文件包含应用中所有的类,系统会通过DefineClass
从dex
中读取字节码并定义类。这个过程通常会调用到 Android 的
Runtime
库中的ClassLoader
,它的defineClass()
方法负责将.dex
文件中的字节码转换成Class
对象。```C++
// 在 native 层的 defineClass 实现
Class* defineClass(JNIEnv* env, const char* name, jobject loader, const unsigned char* buf, int len);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
通过 `DefineClass` 过程,虚拟机会创建一个 `Class` 对象,该对象包含类的基本信息,比如类名、父类、接口等元数据。
**LoadClass->**
`LoadClass` 是类加载的核心步骤,它负责查找并加载指定的类,并将其标记为“已加载”状态。
- 如果类已经被加载过,`LoadClass` 会直接返回该类的 `Class` 对象;如果类没有被加载,则会通过 `ClassLoader` 执行实际的类加载操作。
- 在 `LoadClass` 过程中,系统会遍历父类和接口,递归加载相关依赖的类,以确保依赖类都被正确加载。
在 `Native` 层,`LoadClass` 的实现调用了 `Java` 层的 `ClassLoader.loadClass()` 方法,该方法会在系统类加载器和自定义类加载器之间进行选择,确保类从合适的源(如系统 `jar` 包或应用的 `dex` 文件)中加载。
**LoadClassMembers->**
在类被加载后,系统会进入 **`LoadClassMembers`** 阶段,这个阶段用于加载类中的字段和方法定义(即成员变量和方法)。
- **字段加载**:系统会为类的所有字段分配内存,并初始化字段的默认值(对于对象引用通常为 `null`,对于基本数据类型如 `int` 为 `0`)。
- **方法加载**:系统会为类的每个方法加载其字节码或原生实现。
在 `LoadClassMembers` 阶段,系统会遍历类的结构信息,将类的字段和方法转换成虚拟机的内部格式。系统会生成方法表(Method Table)和字段表(Field Table),为后续的调用和操作提供必要的数据结构。
```java
// native 层的 loadClassMembers 函数
void loadClassMembers(JNIEnv* env, jclass clazz) {
// 通过 JNI 获取类的字段和方法,并生成内部表示
}
LoadMethod–>
LoadMethod
是 LoadClassMembers
的一部分,专门用于加载类中的方法。
- 每个方法都会被转换成虚拟机的指令集格式或其对应的
Native
代码(如果是native
方法)。 - 在
LoadMethod
过程中,虚拟机会为每个方法生成一个方法描述符,用于标识方法的名称、参数和返回类型。 - 对于普通的 Java 方法,系统会将
.dex
文件中的dalvik
或ART
字节码转换成虚拟机内部的指令格式。 - 对于
native
方法,会调用JNI
的RegisterNativeMethods()
方法,来将该方法与其对应的 C/C++ 函数绑定起来。
1 | // 加载每个方法的字节码或者注册 native 方法 |
LinkCode—>
LinkCode
是将方法代码与虚拟机的运行环境链接起来的过程,这也是类加载过程中的最后一个准备阶段。
- 链接字节码:虚拟机会将字节码解析为机器码或解释执行代码,确保方法在调用时能够执行。
- 生成本地机器代码:在现代的 ART 虚拟机上,会在
LinkCode
阶段触发即时编译(JIT 编译),将常用的字节码转换成本地机器码,以提高执行效率。 - 方法引用解析:在
LinkCode
过程中,虚拟机会解析类中的符号引用(例如方法调用、字段访问),并将其链接到实际的内存地址上。
在 LinkCode
完成后,类的所有方法和字段都可以被有效地调用和访问。这个阶段确保类的所有依赖已经被解析,类的字节码能够正常运行。
1 | // 链接代码 |
Execute
完成以上步骤后,类已经加载完毕,可以开始执行实际的代码。Execute
是整个类加载过程的最终阶段。
- 在
Execute
阶段,虚拟机会通过调用特定的Entry Point
(即方法入口)来执行代码。 - 当调用某个方法时,虚拟机会在方法表中查找该方法的入口地址,然后开始执行对应的字节码或机器码。
- 若方法执行过程中需要访问其他类,会触发递归加载,确保所有依赖类都已完成加载和链接。
执行的过程通常分为解释执行和 JIT 编译执行两种。ART 虚拟机会通过即时编译(JIT)或预编译(AOT)来将常用的代码编译成机器码,以提升应用的执行效率。
流程
DefineClass:定义类,加载
.dex
文件中的字节码,并创建Class
对象。LoadClass:加载类,确保类及其依赖类都已经加载到内存中。
LoadClassMembers:加载类的成员(字段和方法),构建类的字段表和方法表。
LoadMethod:加载类的方法,解析方法的字节码或绑定
native
方法。LinkCode:链接类代码,将字节码解析为机器码,生成可执行代码。
Execute:执行代码,通过方法入口开始执行字节码或机器码。
类加载:隐式加载和显式加载
隐式加载
是指由系统自动完成类的加载过程。它是类加载最常见的一种形式.
由 JVM 自动管理:程序员无需显式调用类加载方法,JVM 根据需求自动加载类。
按需加载:只有当程序运行到需要某个类时,虚拟机才会加载该类。
(1)创建类的实例,也就是new一个对象
(2)访问某个类或接口的静态变量,或者对该静态变量赋值
(3)调用类的静态方法
(4)反射Class.forName(“android.app.ActivityThread”)
显示加载:
是指通过编程方式显式地告诉 JVM 加载某个类。显式加载通常使用 反射机制 实现,例如通过 Class.forName()
或自定义 ClassLoader
。
(1)使用Loadclass()加载不会初始化,允许开发者继承 ClassLoader
并实现自己的类加载逻辑。
(2)使用forName()加载会初始化。自动触发类的静态代码块和静态字段初始化。
(3)通过ClassLoader.loadClass()可以使用已有的类加载器(如系统类加载器)显式加载类。
Class.forName 和 ClassLoader.loadClass加载有何不同:
(1)ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作
(2)Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作
隐式加载与显式加载的对比
特点 | 隐式加载 | 显式加载 |
---|---|---|
触发方式 | 由 JVM 自动触发 | 由程序员显式调用 |
典型场景 | 常规类加载(new 、静态字段、继承等) |
反射、动态代理、自定义类加载器等 |
初始化行为 | 自动执行静态代码块和静态字段的初始化 | 可控制是否初始化 |
灵活性 | 较低,完全依赖 JVM 机制 | 高,可按需实现自定义逻辑 |
实现复杂度 | 简单,适合常见场景 | 较高,需要了解类加载器的实现细节 |
典型方法 | 隐式由 JVM 调用类加载器的 loadClass() 方法 |
Class.forName() 、ClassLoader.loadClass() 等 |