【碎片八股文 #004】Activity 是如何从点击图标到启动界面的?

74 阅读5分钟

【碎片八股文 #004】Activity 是如何从点击图标到启动界面的?

一、面试题原文

面试官:  从用户点击桌面图标,到 Activity 界面显示出来,中间经历了哪些步骤?

候选人:  先启动应用,然后创建 Activity……具体流程不太清楚。

面试官心里想:  能说出 Launcher、AMS、Zygote fork、ApplicationThread 就算及格了。


二、常见误答

很多人只知道"点击图标会启动 Activity",但说不清楚:

  • 谁负责启动 Activity?
  • 应用进程是什么时候创建的?
  • onCreate() 是谁调用的?

这些都是面试高频追问点。


三、正确理解

Activity 启动的核心角色

启动一个 Activity 涉及多个系统组件协作:

角色职责
Launcher桌面应用,响应点击事件
ActivityManagerService (AMS)系统服务,管理所有 Activity 的生命周期
Zygote孵化器进程,负责 fork 新的应用进程
ApplicationThread应用进程的 Binder 接口,接收 AMS 的指令
ActivityThread应用主线程,实际执行 Activity 创建

核心流程可以分为两个阶段:

  1. 进程启动阶段(如果应用进程不存在)
  2. Activity 创建阶段

四、完整启动流程

阶段一:进程启动(如果应用未运行)

1. Launcher 点击图标
   ↓
2. Launcher 通过 Binder 调用 AMS.startActivity()
   ↓
3. AMS 检查目标应用进程是否存在
   ↓
4. 进程不存在,AMS 请求 Zygote fork 新进程
   ↓
5. Zygote fork 出应用进程
   ↓
6. 新进程启动后,创建 ActivityThread(主线程)
   ↓
7. ActivityThread 通过 Binder 连接到 AMS

阶段二:Activity 创建

8. AMS 通过 Binder 通知 ApplicationThread 启动 Activity
   ↓
9. ApplicationThread 向 Handler 发送 LAUNCH_ACTIVITY 消息
   ↓
10. ActivityThread 处理消息,调用 performLaunchActivity()
   ↓
11. 通过反射创建 Activity 实例
   ↓
12. 创建 Application(如果未创建)
   ↓
13. 创建 ContextImpl 并关联到 Activity
   ↓
14. 调用 Activity.attach() 初始化 Window
   ↓
15. 调用 Activity.onCreate()
   ↓
16. 调用 Activity.onStart()
   ↓
17. 调用 Activity.onResume()
   ↓
18. WindowManager 将 DecorView 添加到窗口
   ↓
19. ViewRootImpl 开始绘制,界面显示

五、图解核心流程

跨进程通信示意图

┌──────────────┐           ┌──────────────┐           ┌──────────────┐
│   Launcher   │           │     AMS      │           │    Zygote    │
│   (进程1)     │           │  (系统进程)   │           │   (孵化器)   │
└──────┬───────┘           └──────┬───────┘           └──────┬───────┘
       │                          │                          │
       │ 1. startActivity()       │                          │
       ├─────── Binder ──────────→│                          │
       │                          │ 2. 请求创建进程            │
       │                          ├────── Socket ───────────→│
       │                          │                          │ 3. fork()
       │                          │                          │
       │                          │ 4. 进程创建成功            │
       │                          │←────────────────────────┤
       │                          │                          │
       
       
┌──────────────────────────────────────────────────────────────────┐
│                       新创建的应用进程                              │
│                                                                  │
│  ActivityThread.main()                                           │
│      ↓                                                           │
│  创建 ApplicationThread (Binder 对象)                             │
│      ↓                                                           │
│  通过 Binder 注册到 AMS                                            │
│      ↓                                                           │
│  接收 AMS 的启动 Activity 指令                                     │
│      ↓                                                           │
│  Handler 处理 LAUNCH_ACTIVITY 消息                                │
│      ↓                                                           │
│  performLaunchActivity()                                         │
│      ↓                                                           │
│  反射创建 Activity 实例                                            │
│      ↓                                                           │
│  调用生命周期: onCreate() → onStart() → onResume()                 │
└──────────────────────────────────────────────────────────────────┘

六、关键技术点解析

1. 为什么要通过 Zygote fork 进程?

Zygote 是预加载进程,启动时已经加载了:

  • 系统常用的类和资源
  • 共享库(libc、libandroid_runtime)

fork 的好处:

  • 通过写时复制(Copy-On-Write)共享内存,启动快
  • 新进程继承 Zygote 的类加载器,不需要重新加载系统类

如果不用 Zygote:  每次启动应用都要重新加载系统库,非常慢。

2. AMS 和 ApplicationThread 的通信

这是典型的 双向 Binder 通信

AMS (System Server 进程)  ←─ Binder ─→  ApplicationThread (应用进程)

- AMS 持有 ApplicationThread 的 Binder 引用
- ApplicationThread 持有 AMS 的 Binder 引用
- 双方可以互相调用对方的方法

具体调用:

// AMS 通知应用启动 Activity
app.thread.scheduleLaunchActivity(...);  // 跨进程调用

// ApplicationThread 接收指令
public void scheduleLaunchActivity(...) {
    sendMessage(H.LAUNCH_ACTIVITY, r);  // 发送到 Handler
}

3. 为什么要用 Handler 中转?

ApplicationThread 运行在 Binder 线程池,不是主线程。

但 Activity 的生命周期必须在 主线程 执行,所以通过 Handler 切换到主线程:

// Binder 线程
ApplicationThread.scheduleLaunchActivity() 
    ↓
// 发送消息到主线程
Handler.sendMessage(LAUNCH_ACTIVITY)
    ↓
// 主线程处理
ActivityThread.handleLaunchActivity()
    ↓
// 调用生命周期
Activity.onCreate()

4. Activity 实例是如何创建的?

通过 反射 创建:

// ActivityThread.performLaunchActivity()
ClassLoader cl = appContext.getClassLoader();
Activity activity = (Activity) cl.loadClass(className).newInstance();

// 调用 attach() 初始化 Window
activity.attach(appContext, this, ...);

// 调用 onCreate()
instrumentation.callActivityOnCreate(activity, ...);

关键点:  Activity 的构造函数不能有参数,因为是反射调用无参构造。


七、延伸提问

1. 如果应用进程已经存在,流程有什么不同?

跳过 进程启动阶段,直接从第 8 步开始:

AMS 通知已有进程的 ApplicationThread 启动新 Activity
    ↓
ActivityThread 创建 Activity 实例并执行生命周期

这就是为什么后续打开 Activity 比首次启动快。

2. 启动模式(launchMode)在哪里生效?

在 AMS 的 startActivity() 方法中

AMS 会检查启动模式:

  • standard:每次创建新实例
  • singleTop:栈顶复用,调用 onNewIntent()
  • singleTask:栈内复用,清除上面的 Activity
  • singleInstance:独立任务栈

关键代码位置:  ActivityStarter.startActivityUnchecked()

3. Application 是什么时候创建的?

在 第一个 Activity 启动前

// ActivityThread.handleBindApplication()
Application app = instrumentation.newApplication(cl, appClass, appContext);
app.onCreate();  // 调用 Application.onCreate()

结论:  Application.onCreate() 在任何 Activity 之前执行。

4. 为什么冷启动慢?

冷启动包含的步骤:

  1. Zygote fork 进程(耗时)
  2. 加载 Application 和 Activity 类(耗时)
  3. 执行 Application.onCreate()(可能有耗时操作)
  4. 执行 Activity.onCreate()(布局渲染耗时)

优化方向:

  • 减少 Application.onCreate() 中的初始化
  • 使用启动页占位
  • 延迟加载非必要资源

八、记忆口诀

"点图标找 Launcher,Launcher 问 AMS;AMS 叫 Zygote,fork 进程快又稳;ApplicationThread 收指令,Handler 转主线程;反射创建 Activity,生命周期按序行。"


九、碎片笔记

核心关键词:  Launcher、AMS、Zygote、ApplicationThread、ActivityThread、反射、Handler

重点记忆:

  • Zygote 通过 fork 创建应用进程,利用 Copy-On-Write 提速
  • AMS 通过 Binder 与 ApplicationThread 双向通信
  • ApplicationThread 在 Binder 线程,通过 Handler 切换到主线程
  • Activity 通过反射创建,所以构造函数不能有参数

实际应用:

  • 优化冷启动:减少 Application.onCreate() 初始化
  • 避免在 Activity.onCreate() 中做耗时操作
  • 理解启动模式在 AMS 层生效,不是 Activity 自己控制

今天的碎片,帮你面试少挂一次。


下一篇预告:  【碎片八股文 #005】Hermes 引擎和 JSC 有什么区别?