Android 进程与线程模型深度剖析(1):引言:并发执行的基石与挑战

本文是「Android 进程与线程模型深度剖析」系列的第 1 篇,共 3 篇。

引言:并发执行的基石与挑战

在 Android 系统中,所有应用程序代码都运行在特定的进程和线程上下文中。进程提供资源隔离和独立运行的环境,线程则是 CPU 调度的基本单位,负责执行具体的代码指令。理解 Android 如何创建、管理、调度进程(包括其生命周期、优先级和终止机制),以及如何在进程内有效地组织和管理线程(主线程、Binder 线程、后台线程),包括它们之间的同步与通信,对于构建稳定、流畅、响应迅速的应用至关重要。

对于 Android 专家而言,仅仅掌握 Thread 的基本用法或知道 UI 操作要在主线程执行是远远不够的。必须深入理解 Linux 进程/线程的底层基础、Android 独特的进程生命周期与 OOM 优先级调整机制(oom_adj)、主线程的至高地位与性能瓶颈、Binder 线程池的工作原理、高级后台任务处理策略、Handler/Looper 消息机制的内部细节、复杂场景下的线程同步技术,以及 ANR(Application Not Responding)的系统性分析方法。这种深层次的理解是解决并发问题、优化响应速度、分析系统级异常行为的基础。

本文将深入探讨 Android 的进程与线程模型,涵盖以下内容:

  • 底层基础:Linux 进程与线程概念回顾
  • Android 进程:Zygote 孵化、生命周期、优先级与 OOM Killer(oom_adj)
  • 主线程剖析:UI 线程的关键职责与性能约束
  • Binder 线程:处理 IPC 的核心线程池
  • 后台线程策略:ExecutorService、Coroutines 等现代并发方案
  • Handler 机制详解:Looper、MessageQueue、ThreadLocal 的内部运作
  • 高级同步:锁、原子类、并发工具(CountDownLatch、Semaphore 等)的原理与应用
  • ANR 深度分析:系统性地诊断和解决应用无响应问题

一、底层基础:Linux 进程与线程模型

Android 构建于 Linux 内核之上,其进程和线程模型直接继承自 Linux。

进程(Process)

  • 是程序执行时的一个实例
  • 拥有独立的虚拟地址空间、内存、数据栈以及文件描述符等系统资源
  • 进程间相互隔离,通信需要通过 IPC 机制(如 Binder、Socket、Pipe)
  • Linux 通过 fork() 系统调用创建子进程(复制父进程地址空间),通常子进程会接着调用 exec() 系列系统调用来加载并执行新的程序镜像

线程(Thread)

  • 是进程内的一个执行单元,是 CPU 调度的基本单位
  • 同一进程内的线程共享该进程的虚拟地址空间、内存资源(代码段、数据段、堆内存)和文件描述符
  • 每个线程拥有自己独立的程序计数器、寄存器、线程栈(用于存储局部变量和函数调用信息)
  • 线程间的切换(上下文切换)通常比进程切换开销小得多
  • Linux 内核中,线程(Lightweight Process, LWP)是通过 clone() 系统调用以特定参数创建的,它提供了比 fork() 更灵活的资源共享选项。用户空间通常使用 POSIX 线程库(pthread)来创建和管理线程

Android 应用上下文

每个 Android 应用默认运行在一个独立的 Linux 进程中,拥有唯一的 UID(用户 ID)和 GID(组 ID),实现了应用间的安全沙箱隔离。应用内的所有代码,无论是 Java/Kotlin 还是 Native,都执行在属于该进程的某个线程上。

二、Android 进程模型:被管理的生命周期与优先级

Android 系统对应用进程的管理远比标准 Linux 更为严格和主动,核心目标是保障系统流畅性和用户体验。

Zygote 孵化进程

如前所述,所有应用进程(以及 SystemServer)都由 Zygote 进程通过 fork() 创建。这使得新进程能够快速启动并共享内存(Copy-on-Write)。

进程生命周期与状态(受 AMS 管理)

Android 系统根据应用组件的状态和用户交互情况,将进程大致分为几个优先级类别,这直接决定了进程在内存不足时被杀死的可能性。

前台进程(Foreground Process)

  • 用户当前正在交互的应用(顶部的 Activity 处于 Resumed 状态)
  • 托管着与用户交互的 Activity 绑定的 Service
  • 托管着调用了 startForeground() 的 Service(显示持续通知)
  • 托管着正在执行生命周期回调(onCreate、onStart、onDestroy)的 Service
  • 托管着正在执行 onReceive() 的 BroadcastReceiver

优先级最高,系统只有在万不得已(内存极度匮乏)时才会杀死它。

可见进程(Visible Process)

  • 拥有用户可见但不在前台的 Activity(例如,Activity 被一个非全屏的 Dialog 或 Activity 部分遮挡,处于 Paused 状态)
  • 托管着与可见 Activity 绑定的 Service

优先级很高,除非为了保证前台进程的运行,否则不会被杀死。

服务进程(Service Process)

  • 托管着通过 startService() 启动且仍在运行的 Service,并且该 Service 不属于前台或可见进程类别

优先级高于后台进程,但低于可见进程。运行时间过长且不重要的服务进程也可能被回收。

缓存进程(Cached Process)

  • 不包含任何前台、可见或服务组件。通常包含用户已退出但仍在内存中(Activity 处于 Stopped 状态)的应用,以便下次快速启动

优先级最低,是系统内存不足时最先被杀死的对象。 缓存进程内部也根据 LRU(最近最少使用)等策略进一步细分优先级(如空进程、前一个应用进程、主屏幕进程等)。

(图示:Android 进程优先级)

Most Important (Least likely to be killed)
          ^
          |
+-------------------------+
|  Foreground Process     |  (Activity Resumed, Foreground Service)  <- oom_adj ~ 0
+-------------------------+
          |
+-------------------------+
|  Visible Process        |  (Activity Paused but Visible)           <- oom_adj ~ 100-200
+-------------------------+
          |
+-------------------------+
|  Service Process        |  (Started Service running)               <- oom_adj ~ 500+
+-------------------------+
          |
+-------------------------+
|  Cached Process (LRU)   |  (Activity Stopped/Destroyed, Empty)     <- oom_adj ~ 900+
+-------------------------+
          |
          V
      Least Important (Most likely to be killed)

OOM Killer 与 oom_adj 分数

LMK(Low Memory Killer):Android 内核中的一个驱动或机制,负责在系统内存低于特定阈值时,根据进程的优先级杀死进程以回收内存。

oom_adj(Out-of-Memory Adjustment)分数:这是 AMS(ActivityManagerService)计算并设置给每个进程的一个关键内核参数(位于 /proc/<pid>/oom_score_adj)。它的值范围大致在 -1000(永不杀死,如系统进程)到 +1000(最容易杀死,如空缓存进程)。oom_adj 值越低,进程越重要,越不容易被 LMK 杀死。

动态调整:AMS 会根据进程中运行的组件状态(Activity 是否可见、Service 是否前台、是否有绑定连接等)动态地调整进程的 oom_adj 分数。例如,当 Activity 进入后台,其所在进程的 oom_adj 会升高;当 Service 调用 startForeground(),其进程的 oom_adj 会降低。

理解 oom_adj 的计算和影响,对于以下场景至关重要:

  • 后台任务设计:选择合适的后台机制(如 Foreground Service、WorkManager)以保证任务在低内存情况下尽可能存活
  • 分析进程被杀:当应用进程意外消失时,检查其被杀前的 oom_adj 分数和系统内存状态是关键线索
  • 优化内存占用:减少应用的内存占用可以降低整体系统内存压力,间接提高自身进程的存活率

多进程应用

场景:通过在 AndroidManifest.xml 中使用 android:process 属性,可以将应用的不同组件(Activity、Service、Receiver、Provider)运行在不同的进程中。

优点:隔离性(一个进程崩溃不影响其他进程)、可能绕过单进程内存上限(但整体内存消耗通常更高)、安全性(如将敏感操作放在独立进程)。

挑战

  • IPC 开销:进程间通信必须通过 Binder(AIDL)、Messenger、ContentProvider 或 Socket 等机制,带来额外的性能开销和实现复杂度
  • 内存增加:每个进程都有独立的虚拟机实例和运行时开销,总体内存占用高于单进程
  • 管理复杂:需要仔细设计进程间依赖、生命周期同步、数据共享等问题

多进程是一种架构选择,需要仔细权衡其带来的好处和成本,通常只在有明确需求(如稳定性隔离、特殊内存需求)时才采用。


下一篇我们将探讨「Android 主线程(UI 线程):心脏与瓶颈」,敬请关注本系列。

「Android 进程与线程模型深度剖析」系列目录

  1. 引言:并发执行的基石与挑战(本文)
  2. Android 主线程(UI 线程):心脏与瓶颈
  3. 高级同步与线程安全