抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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 系统的服务。

  1. 初始化系统服务ZygoteInit.main 方法首先会启动 System Server 进程,这是 Android 系统的核心服务进程。System Server 负责启动和管理系统中几乎所有的核心服务,包括 Activity Manager Service、Package Manager Service、Window Manager Service 等。
  2. 启动 Zygote SocketZygoteInit.main 方法还会创建一个 Socket 接口,名为 Zygote Socket。这个 Socket 用于接收来自 Activity Manager Service 的新应用启动请求。每当用户启动应用时,Activity Manager 会通过此 Socket 向 Zygote 发送请求,Zygote 再 fork 新进程来启动该应用。
  3. 等待应用请求:完成 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来启动应用程序进程,即进入到ActivityThreadmain方法,创建并启动主线程的消息循环,进入阻塞状态。

5应用程序进程的运行

ActivityThread.main() 被调用后,ActivityThread 进程会继续执行以下几个关键步骤:

  • **创建 HandlerLooper**:
    • Android 的 HandlerLooper 构成了一个消息队列机制,它们帮助应用程序处理异步事件。每个应用进程都会有一个主线程,主线程会有一个 Looper,负责从消息队列中读取消息并通过 Handler 来处理。
    • 在应用启动时,ActivityThread 会创建一个 Handler 对象,它负责处理 UI 事件和系统消息。
  • **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
2
3
4
5
public class MyApplication extends Application {
public MyApplication() {
// 应用的构造函数
}
}

Application.attachBaseContext()——>

attachBaseContext()Application 类中的一个重要方法,它会在 onCreate() 之前被调用。这个方法主要是为了将 Context 绑定到 Application 上。实际上,Context 是 Android 中非常重要的一个概念,它提供了应用环境的一些功能,如访问资源、启动活动、访问文件系统等。

在调用 attachBaseContext() 时,系统会将 Context 对象传递给 Application,并确保 Application 在执行任何任务之前已经具有了适当的上下文。

1
2
3
4
5
@Override
public void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 在这里执行一些需要在 onCreate() 前完成的操作
}

attachBaseContext() 方法常常用于执行一些与上下文(Context)相关的初始化操作,像是配置一些全局的设置,或者注入一些依赖等。

Application.onCreate()函数–>

onCreate() 方法是 Application 类的核心方法之一,它在应用的整个生命周期内只会被调用一次。它会在 attachBaseContext() 调用之后执行,用于执行更复杂的初始化操作,例如全局配置、SDK 初始化、资源加载等。

1
2
3
4
5
@Override
public void onCreate() {
super.onCreate();
// 初始化应用级的资源或服务
}

最后才会进入MainActivity中的attachBaseContext函数、onCreate函数等等

MainActivity 会首先调用 attachBaseContext() 方法,它的作用和 Application 类中的 attachBaseContext() 类似:为 Activity 提供合适的 Context。但与 ApplicationattachBaseContext() 不同,ActivityattachBaseContext() 是在创建 Activity 之前调用的,因此可以用来处理一些与上下文相关的初始化工作。

在完成 attachBaseContext() 调用后,系统会调用 MainActivityonCreate() 方法。此时,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
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
27
28
29
30
//路径:/libcore/ojluni/src/main/java/java/lang/ClassLoader.java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 先检查是否已经加载过了该类
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {//没有加载该类,且存在父加载器,交给父加载器处理
c = parent.loadClass(name, false);
} else {//没有加载该类,且不存在父加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
//父加载器处理失败,自己来找
c = findClass(name);
}
}
return c;
}

private Class<?> findBootstrapClassOrNull(String name)
{
return null;
}

作用:

1
2
3
(1) 防止同一个.class文件重复加载
(2) 对于任意一个类确保在虚拟机中的唯一性.由加载它的类加载器和这个类的全类名一同确立其在Java虚拟机中的唯一性
(3) 保证.class文件不被篡改,通过委派方式可以保证系统类的加载逻辑不被篡改

DefineClass->

DefineClass 是加载类的第一步,通常是在 Native 层调用的。

  • DefineClass 的主要作用是将类的字节码(从 .dex 文件中读取)转换成一个可以被虚拟机识别的对象格式。

  • 在 Android 中,.dex 文件包含应用中所有的类,系统会通过 DefineClassdex 中读取字节码并定义类。

  • 这个过程通常会调用到 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–>

LoadMethodLoadClassMembers 的一部分,专门用于加载类中的方法。

  • 每个方法都会被转换成虚拟机的指令集格式或其对应的 Native 代码(如果是 native 方法)。
  • LoadMethod 过程中,虚拟机会为每个方法生成一个方法描述符,用于标识方法的名称、参数和返回类型。
  • 对于普通的 Java 方法,系统会将 .dex 文件中的 dalvikART 字节码转换成虚拟机内部的指令格式。
  • 对于 native 方法,会调用 JNIRegisterNativeMethods() 方法,来将该方法与其对应的 C/C++ 函数绑定起来。
1
2
3
4
// 加载每个方法的字节码或者注册 native 方法
void loadMethod(JNIEnv* env, jmethodID method) {
// 加载方法实现的字节码或原生代码
}

LinkCode—>

LinkCode 是将方法代码与虚拟机的运行环境链接起来的过程,这也是类加载过程中的最后一个准备阶段。

  • 链接字节码:虚拟机会将字节码解析为机器码或解释执行代码,确保方法在调用时能够执行。
  • 生成本地机器代码:在现代的 ART 虚拟机上,会在 LinkCode 阶段触发即时编译(JIT 编译),将常用的字节码转换成本地机器码,以提高执行效率。
  • 方法引用解析:在 LinkCode 过程中,虚拟机会解析类中的符号引用(例如方法调用、字段访问),并将其链接到实际的内存地址上。

LinkCode 完成后,类的所有方法和字段都可以被有效地调用和访问。这个阶段确保类的所有依赖已经被解析,类的字节码能够正常运行。

1
2
3
4
// 链接代码
void linkCode(JNIEnv* env, jclass clazz) {
// 解析字节码,生成 JIT 编译代码
}

Execute

完成以上步骤后,类已经加载完毕,可以开始执行实际的代码。Execute 是整个类加载过程的最终阶段。

  • Execute 阶段,虚拟机会通过调用特定的 Entry Point(即方法入口)来执行代码。
  • 当调用某个方法时,虚拟机会在方法表中查找该方法的入口地址,然后开始执行对应的字节码或机器码。
  • 若方法执行过程中需要访问其他类,会触发递归加载,确保所有依赖类都已完成加载和链接。

执行的过程通常分为解释执行和 JIT 编译执行两种。ART 虚拟机会通过即时编译(JIT)或预编译(AOT)来将常用的代码编译成机器码,以提升应用的执行效率。

流程

  1. DefineClass:定义类,加载 .dex 文件中的字节码,并创建 Class 对象。

  2. LoadClass:加载类,确保类及其依赖类都已经加载到内存中。

  3. LoadClassMembers:加载类的成员(字段和方法),构建类的字段表和方法表。

  4. LoadMethod:加载类的方法,解析方法的字节码或绑定 native 方法。

  5. LinkCode:链接类代码,将字节码解析为机器码,生成可执行代码。

  6. 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()

评论