Android 进程与线程模型深度剖析
引言:并发执行的基石与挑战
在 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 线程):心脏与瓶颈
应用进程启动时创建的第一个线程,通常被称为主线程或 UI 线程。
核心职责
- UI 交互处理:分发和处理用户输入事件(触摸、按键)
- UI 绘制:执行 Choreographer 回调,进行 Measure、Layout、Draw
- 组件生命周期:执行 Activity、Service、BroadcastReceiver 等组件的生命周期回调方法(onCreate、onStart、onResume、onReceive 等)
- 主 Looper 任务:执行通过与主线程 Looper 关联的 Handler post 过来的 Runnable 或 Message
黄金法则:永不阻塞主线程
原因:主线程负责处理所有与用户交互和界面更新的任务。任何耗时操作(网络请求、数据库读写、复杂计算、文件 IO,甚至某些有争议的锁等待)如果发生在主线程,都会阻止其处理新的 UI 事件或绘制请求。
后果:
- 轻微:掉帧(Jank)、动画卡顿、界面失去响应
- 严重:触发 ANR(Application Not Responding)对话框,用户可能选择强制关闭应用
四、Binder 线程:跨进程通信的执行者
如 Binder 章节所述,当其他进程(包括系统服务)通过 Binder 调用当前进程提供的服务时,请求是在专门的 Binder 线程上执行的。
Binder 线程池:每个提供 Binder 服务的进程维护一个线程池(由 libbinder 和内核驱动管理),用于并发处理传入的 IPC 调用。默认上限通常是 15 个线程(主线程除外)。
执行上下文:AIDL 接口方法的实现代码(或 Binder.onTransact)运行在 Binder 线程上。
核心规则
- 禁止耗时操作:同样地,不能在 Binder 线程中执行可能阻塞的操作,否则会耗尽线程池资源,导致后续的 IPC 请求(包括来自系统的重要调用)无法被及时处理,引发死锁或 ANR。必须将耗时任务异步化
- 禁止直接 UI 更新:Binder 线程不能直接操作 UI 组件。需要通过 Handler 将 UI 更新任务切换回主线程执行
- 线程安全:如果 Binder 方法访问了可能被主线程或其他后台线程并发访问的共享数据,必须采取正确的同步措施
五、后台线程策略:将耗时任务请出主线程
为了遵守「不阻塞主线程/Binder 线程」的规则,必须将耗时任务放到后台线程执行。
基本 Thread + Runnable
最基础的方式,灵活但管理复杂。需要手动处理线程生命周期、中断、错误、与主线程通信等,容易出错。
ExecutorService / ThreadPoolExecutor
推荐方式:提供线程池管理,复用线程,避免频繁创建销毁线程的开销。
灵活性:可以配置核心线程数、最大线程数、线程存活时间、任务队列类型(有界/无界)、拒绝策略。
使用:通过 Executors 工厂类创建常用类型线程池(newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor),或直接构造 ThreadPoolExecutor 进行精细控制。使用 submit() 或 execute() 提交 Runnable 或 Callable 任务。
根据任务类型(CPU 密集型 vs IO 密集型)合理配置线程池大小(CPU 密集型通常接近 CPU 核心数,IO 密集型可以更大);选择合适的任务队列和拒绝策略;注意线程池的生命周期管理(适时 shutdown())。
AsyncTask(已弃用)
已弃用,不再推荐使用。
IntentService(已弃用)/ JobIntentService
已弃用,建议使用 WorkManager 等替代方案。
Kotlin Coroutines(协程)——现代首选
- 轻量级:协程是运行在线程之上的、可挂起和恢复的计算单元,比线程更轻量。可以创建大量协程而不会耗尽系统资源
- 简化异步:使用
suspend关键字使得异步代码写起来像同步代码一样直观 - 结构化并发:提供了 CoroutineScope(如
viewModelScope、lifecycleScope)来管理协程的生命周期,与组件生命周期绑定,自动取消,极大减少泄漏风险 - 调度器(Dispatchers):方便地在不同线程(
Dispatchers.Main、Dispatchers.IO、Dispatchers.Default)之间切换协程执行上下文 - 生态:与 Jetpack 库(LiveData、ViewModel、Room 等)深度集成。是目前 Kotlin 开发 Android 应用进行并发编程的首选方案
RxJava / RxAndroid
- 响应式编程:基于观察者模式,使用强大的操作符链式组合、转换、过滤异步事件流
- 优点:处理复杂异步逻辑、多数据源合并、背压等场景非常强大
- 缺点:学习曲线较陡峭,概念较多(Observable、Operator、Scheduler 等),代码可能不易理解
六、Handler、Looper、MessageQueue 详解:Android 线程通信的基石
这是 Android 框架内部广泛使用的线程通信和任务调度机制,尤其用于在不同线程间安全地传递消息和执行任务(特别是切换回主线程)。
核心组件
Message:携带少量数据(what、arg1、arg2、obj——避免用 obj 传递大对象,考虑 setData(Bundle))或一个 Runnable 任务。包含一个 target 字段指向处理它的 Handler。
MessageQueue:每个拥有 Looper 的线程都有一个 MessageQueue。它按执行时间顺序存储待处理的 Message。当队列为空时,它会通过底层的 Linux epoll 机制高效地阻塞等待,直到新消息到来或超时。这是 Looper.loop() 不会空耗 CPU 的原因。
Looper:每个线程最多只能有一个 Looper(通过 ThreadLocal 存储)。它的核心是 loop() 方法,该方法进入一个死循环,不断地从其 MessageQueue 中取出下一条消息(queue.next()),如果消息不为空,则将其分发给消息的目标 Handler(msg.target.dispatchMessage(msg))。
Handler:
- 创建:在哪个线程创建 Handler 实例,它默认就与该线程的 Looper 关联(除非显式指定 Looper)
- 发送/发布:提供
post(Runnable)、postDelayed()、sendMessage()、sendMessageDelayed()、obtainMessage().sendToTarget()等方法,将 Runnable 包装成 Message 或直接将 Message 放入目标 Looper 的 MessageQueue 中 - 处理:
dispatchMessage(Message msg)方法负责执行。如果 Message 有关联的 Runnable,则执行 Runnable;否则,如果 Handler 创建时传入了 Callback 接口,则调用callback.handleMessage();最后,如果前两者都没有,则调用 Handler 子类覆写的handleMessage(Message msg)方法。执行发生在与 Handler 关联的 Looper 所在的线程上
ThreadLocal 的作用:Looper 使用 ThreadLocal<Looper>(sThreadLocal)来确保每个线程拥有自己独立的 Looper 实例。Looper.prepare() 就是向当前线程的 ThreadLocalMap 中存入一个新的 Looper 对象。
经典用途
子线程 → 主线程更新 UI:在子线程创建 new Handler(Looper.getMainLooper()),然后通过此 Handler post 一个更新 UI 的 Runnable。
创建自定义工作线程:
class WorkerThread extends Thread {
public Handler mHandler; // Handler for this worker thread
@Override
public void run() {
Looper.prepare(); // Associate a Looper with this thread
// Handler created here is associated with the new Looper
mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
// Process messages received on this worker thread
Log.d("WorkerThread", "Processing message: " + msg.what);
}
};
Looper.loop(); // Start the message loop, blocks until Looper.quit()
Log.d("WorkerThread", "Looper finished.");
}
}
// Usage:
WorkerThread worker = new WorkerThread();
worker.start();
// Wait until handler is created (use CountDownLatch or similar for safety)
// ...
// Send messages from other threads to the worker thread's handler
worker.mHandler.obtainMessage(MSG_DO_WORK, someData).sendToTarget();
// ...
// To stop the worker thread's looper:
// worker.mHandler.getLooper().quitSafely(); // Or quit()
HandlerThread:Android 提供的一个便利类,封装了上述创建带 Looper 的工作线程的逻辑。
常见陷阱
- 内存泄漏:在 Activity/Fragment 中使用非静态内部类 Handler,会导致 Handler 隐式持有外部类引用。如果 Handler 发送了延迟消息,即使 Activity 销毁,消息仍在队列中,Handler 和 Activity 都无法被回收。解决方案:使用静态内部类 +
WeakReference<Activity>,或者使用 Lifecycle aware 组件 - 主线程 Looper 阻塞:在主线程 Handler 的
handleMessage或Runnable.run中执行耗时操作 - 消息队列过载:过度发送大量消息可能导致处理延迟
七、高级同步与线程安全
当多个线程访问共享的可变数据时,必须使用同步机制来保证数据的原子性(Atomicity)、可见性(Visibility) 和有序性(Ordering),防止出现数据竞争和不一致状态。
核心概念
- 原子性:一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
- 可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改
- 有序性:程序执行的顺序按照代码的先后顺序执行(编译器和处理器可能会进行指令重排优化,需要同步机制来保证特定情况下的有序性)
同步原语(Primitives)选择与应用
synchronized(内置锁)
- 优点:使用简单,不易出错(自动释放锁)
- 缺点:功能相对有限——锁是不可中断的;不支持公平性;一个锁只能关联一个条件等待队列。适用于简单、低竞争的场景
volatile
- 作用:保证被修饰变量的可见性;禁止指令重排序优化(部分保证有序性)
- 局限:不保证原子性。例如
volatile int i; i++;不是原子操作 - 适用:主要用于状态标志位(如
volatile boolean flag = false;)或确保单次读写操作的可见性。不能替代锁用于保护复合操作
java.util.concurrent.locks.Lock(如 ReentrantLock)
- 优点:功能更强——可中断锁(
lockInterruptibly);可超时锁(tryLock);可实现公平/非公平锁;可关联多个 Condition 对象(用于实现更复杂的等待/通知模式) - 缺点:必须手动在 finally 块中释放锁(
unlock()),否则可能导致死锁 - 适用:需要更灵活的锁控制、可中断性、公平性或多个等待条件的场景。性能在低竞争下与 synchronized 类似,高竞争下通常更好(但具体取决于实现和平台)
ReadWriteLock(如 ReentrantReadWriteLock)
- 场景:读多写少的共享数据。允许多个读线程同时访问,但写操作是互斥的
- 优点:显著提高读操作的并发性
- 注意:实现比 ReentrantLock 更复杂,需要正确使用读锁(
readLock())和写锁(writeLock())
java.util.concurrent.atomic.*
- 场景:对单个变量(计数器、状态标志)进行原子更新
- 原理:利用 CPU 提供的 CAS(Compare-and-Swap)原子指令实现,无锁(Lock-Free),效率高
- 优点:比使用锁更轻量、性能更好
- 局限:只能保证单个变量的原子性,不能保证复合操作的原子性
CountDownLatch
- 场景:一个线程需要等待一个或多个其他线程完成某些操作后才能继续执行。例如,主线程等待多个初始化子任务完成
CyclicBarrier
- 场景:多个线程需要相互等待,直到所有线程都到达一个「屏障点」,然后才能一起继续执行下一步。可以重用。例如,并行计算中,各阶段需要等待所有线程完成
Semaphore(信号量)
- 场景:控制同时访问某个特定资源(如数据库连接池、网络连接数)的线程数量
BlockingQueue
- 场景:生产者-消费者模式。解耦生产者线程和消费者线程,自带同步和阻塞功能
线程安全最佳实践
- 优先考虑不可变性(Immutability):如果共享数据是不可变的(创建后状态不再改变),则天生线程安全,无需同步
- 缩小同步范围:尽量减小锁保护的代码块范围,只保护必要的临界区,以提高并发性
- 锁顺序:如果需要获取多个锁,确保所有线程都以相同的固定顺序获取锁,以避免死锁
- 使用并发容器:
java.util.concurrent包提供了线程安全的集合类(如ConcurrentHashMap、CopyOnWriteArrayList),通常比手动同步 HashMap/ArrayList 更高效、更安全 - 避免持有锁时执行耗时操作或调用外部方法:防止长时间占用锁
八、ANR(Application Not Responding)深度分析
ANR 是 Android 中指示应用严重无响应的信号,是必须能够熟练诊断和解决的问题。
触发条件回顾
- 输入事件超时:5 秒内未处理完输入事件(触摸、按键)
- 广播接收器超时:
onReceive()执行时间过长(前台广播通常 10 秒,后台广播可能 60 秒) - Service 超时:
onCreate()、onStartCommand()、onBind()等关键方法执行时间过长(前台服务 20 秒,后台服务可能 200 秒)
系统性分析步骤
1. 获取 ANR Trace 文件
这是最重要的依据。可以从 /data/anr/traces.txt(需要 root 或通过 adb bugreport),或 Google Play Console 后台获取。
2. 识别 ANR 类型与时间
查看 Trace 文件开头的摘要信息,确认是哪种类型的 ANR(Input timeout、Broadcast timeout、Service timeout)以及发生时间。
3. 主线程(“main”)状态分析
这是分析的核心! 仔细检查「main」线程的堆栈信息:
- 阻塞点:它最终停在了哪个方法调用上?
- IO 操作? 是否在进行文件读写、网络请求、数据库操作(看堆栈中是否有nativePollOnce、socketRead、FileInputStream.read、SQLiteDatabase 相关调用)?
- CPU 密集计算? 堆栈是否显示正在执行复杂的计算逻辑?
- 锁等待? 是否卡在 monitor wait 或 LockSupport.park / Object.wait 等待锁?Trace 中通常会显示「waiting to lock <0x…>(a …)」以及「held by threadid=
」 - Binder 调用? 是否卡在 BinderProxy.transactNative 或 binder_thread_read?如果是,看调用的是哪个服务(通常能从堆栈或 Binder 相关信息看出)。如果目标是系统服务,需要怀疑 SystemServer 是否缓慢或死锁
- GC? 堆栈是否显示正在进行 GC 相关操作?(虽然 GC 本身引起的 ANR 相对少见,但长 GC 暂停可能加剧其他超时)
4. 分析锁持有者线程
如果主线程在等待锁,根据 Trace 中提供的持有者线程 ID(tid),找到该线程的堆栈。分析该线程为什么长时间持有锁?它是否也在进行 IO、计算、等待其他锁(形成死锁链)、或进行 Binder 调用?
5. 分析 Binder 线程
检查所有名为「Binder:<pid>_<n>」的线程堆栈。是否有 Binder 线程卡在耗时的 onTransact 实现中?或者它们是否也在等待锁?
6. 检查其他线程
浏览其他后台线程的堆栈,看是否有异常活动,如参与死锁、消耗过多 CPU 资源导致主线程饥饿等。
7. CPU 负载分析
查看 Trace 文件末尾的 CPU 负载信息(分 User/Kernel/IOwait/IRQ/SoftIRQ)。高 User% 可能意味 CPU 密集计算。高 IOwait% 意味磁盘或网络 IO 瓶颈。高 Kernel% 可能与驱动或系统调用有关。检查 ANR 发生时主线程所在 CPU 核心是否繁忙。
8. 锁信息分析
仔细阅读 Trace 文件中的 Locks 部分,它会列出所有发生争用的锁以及等待和持有线程的信息,是分析死锁的关键。
9. 结合 Systrace/Perfetto
强烈推荐! 如果能在复现 ANR 场景时抓取 Trace,将极大简化分析。Perfetto/Systrace 可以:
- 可视化线程状态:清晰看到主线程在 ANR 发生前长时间处于 Runnable(等待 CPU 调度)还是 Running(执行代码)还是 Sleeping/Blocked 状态
- 定位耗时代码:结合 CPU Time Profiling,直接定位到主线程或锁持有者线程中耗时最长的方法
- 分析锁竞争:直观展示锁的持有和等待关系
- 分析 Binder 调用:显示 Binder 事务的耗时和目标
- 关联系统事件:查看 GC、系统服务活动是否与 ANR 相关
九、结论:掌控并发,驾驭响应
Android 的进程和线程模型是其并发架构的基础,直接关系到应用的资源消耗、稳定性与用户体验。从 Linux 的底层机制,到 Android 系统通过 AMS 和 oom_adj 对进程生命周期的精细管理,再到应用内部主线程、Binder 线程、后台线程的职责划分与协同,以及 Handler/Looper 机制和各种同步原语的应用,共同构成了这个复杂而重要的体系。
Android 专家必须超越基础的线程使用,深刻理解进程优先级与 LMK 的交互,掌握主线程的性能红线,熟悉 Binder 线程池的运作,能够根据场景选择最优的后台并发策略(如 ExecutorService、Coroutines),精通 Handler 机制的内部原理与应用,并能够熟练运用各种高级同步工具解决复杂的线程安全问题。更重要的是,面对 ANR 这一顽疾,能够运用系统性的方法,结合 ANR Trace 和 Perfetto 等工具,抽丝剥茧,定位并解决从应用代码到系统服务交互中可能存在的各种性能瓶颈和死锁问题。
对进程与线程模型的深度掌控,不仅是避免 ANR 的技术保障,更是实现高性能、高并发、高稳定性应用,从而提供卓越用户体验的核心能力。