抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)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 Socket:ZygoteInit.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来启动应用程序进程,即进入到ActivityThread的main方法,创建并启动主线程的消息循环,进入阻塞状态。

5应用程序进程的运行

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

  • **创建 Handler 和 Looper**:
    • Android 的 Handler 和 Looper 构成了一个消息队列机制,它们帮助应用程序处理异步事件。每个应用进程都会有一个主线程,主线程会有一个 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。但与 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
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 文件包含应用中所有的类,系统会通过 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
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() 等

评论