第4章 消息机制与异步处理:Handler、Looper、MessageQueue 原理、AsyncTask、线程池

本章聚焦 Android 消息驱动核心与异步任务管理。面试中 Handler 系列是基础必考,AsyncTask 和线程池考察并发设计能力。以下结构化内容覆盖原理、源码、面试追问及项目实战模板。

Looper.prepare() Looper.loop() Handler.sendMessage MessageQueue.enqueue Handler.dispatch handleMessage()
问题:请描述 Handler、Looper、MessageQueue 的工作原理及它们之间的协作关系
面试官考察点:
  • 是否理解主线程 Looper 自动创建机制
  • 能否说清消息的入队、派发、处理的完整链路
  • 是否知道线程封闭与 Handler 的线程绑定原理
回答框架:
  1. Looper 负责消息循环(prepare + loop),每个线程只能有一个 Looper
  2. MessageQueue 是消息队列(单链表),Looper 从中取出消息
  3. Handler 负责发送消息(sendMessage)和处理消息(handleMessage),发送时放入绑定的 Looper 的 MessageQueue
  4. 主线程 Looper 由 ActivityThread 自动调用 prepareMainLooper
示例回答:
一个线程通过 Looper.prepare() 创建唯一 Looper 并初始化 MessageQueue,然后 Looper.loop() 不断从队列取消息。Handler 通过构造函数绑定该 Looper,发送消息时调用 enqueueMessage 入队。Looper 取出消息后回调 Handler.dispatchMessage,最终执行 handleMessage。
可能追问:
  • 子线程如何创建 Handler?为什么必须 Looper.prepare()?
  • Looper.loop() 为什么不会阻塞主线程?
  • MessageQueue 中消息如何处理同步屏障 barrier?
避坑提醒:不要只说“Handler 用于更新 UI”,忽略线程绑定和消息队列结构。回答要包括 Looper 循环和线程存储(ThreadLocal)机制。
问题:谈谈 AsyncTask 的原理、使用限制及替代方案
面试官考察点:
  • 是否熟悉 AsyncTask 内部线程池 + Handler 的实现
  • 是否了解 Android 11 后 AsyncTask 被废弃的原因
  • 能否给出当前推荐的替代方案(协程、RxJava、线程池)
回答框架:
  1. 原理:AsyncTask 封装了两个线程池(THREAD_POOL_EXECUTOR 用于 doInBackground,SERIAL_EXECUTOR 用于串行任务)和一个 InternalHandler(绑定主线程 Looper)
  2. 限制:容易导致内存泄漏(非静态内部类持有 Activity)、生命周期不同步、串行/并行模式混乱
  3. 替代:推荐 Kotlin 协程(结构化并发 + 自动取消)、RxJava 的 subscribeOn/observeOn、自定义 HandlerThread + 线程池
示例回答:
AsyncTask 内部通过 FutureTask 和 Handler 实现:doInBackground 在子线程执行,onPreExecute/onProgressUpdate/onPostExecute 通过 InternalHandler 切换到主线程。Android 11 标记为废弃,因为相比协程容易内存泄漏且缺乏生命周期感知。
可能追问:
  • AsyncTask 的 execute 和 executeOnExecutor 区别?
  • 如何避免 AsyncTask 的内存泄漏?
  • 如果多个 AsyncTask 同时执行,默认是串行还是并行?
避坑提醒:不要只背概念,要说清 Android 11 后官方推荐协程。示例中要提及 InternalHandler 的 Looper 来自主线程。
问题:如何合理配置线程池参数?Android 中常用的线程池类型及适用场景
面试官考察点:
  • 是否理解核心线程数、最大线程数、阻塞队列、饱和策略
  • 是否能区分 FixedThreadPool、CachedThreadPool、SingleThreadExecutor
  • 是否了解 Android 中的 AsyncTask 内部线程池和 IntentService 的线程模型
回答框架:
  1. 参数:corePoolSize(核心常驻)、maximumPoolSize(最大)、workQueue(阻塞队列)、keepAliveTime + RejectedExecutionHandler
  2. CPU 密集型:core = CPU核心数+1,IO密集型:core = CPU核心数*2
  3. Android 常用:FixedThreadPool(线程数可控)、CachedThreadPool(大量短任务)、SingleThreadExecutor(串行任务)
示例回答:
线程池核心参数根据任务类型调整:CPU 密集设核心数为 N+1,IO 密集设 2N。Android 上耗时操作推荐使用 LruCache + 线程池的组合,避免用 AsyncTask。例如图片加载用 FixedThreadPool 控制并发数,防止 OOM。
可能追问:
  • ThreadPoolExecutor 的饱和策略默认是什么?AbortPolicy 和 DiscardOldestPolicy 区别?
  • 如何动态监控线程池状态?
  • HandlerThread 和普通 Thread 的区别?
避坑提醒:不要只背诵参数名称,要结合 Android 场景(如图片压缩、网络请求)举例。另外注意 AsyncTask 内部线程池是 CPU 密集型还是 IO 密集型?其实是混合,但默认 corePoolSize = CPU+1。

项目案例:基于 Handler + 线程池的图片异步加载框架(ImageLoader)

项目名称高性能 ImageLoader 图片加载组件(SDK)
业务背景解决列表滑动时图片加载卡顿、重复请求、OOM 问题,支持三级缓存(内存、磁盘、网络)
规模指标日活 500 万+,列表项图片请求峰值 800 次/秒,内存缓存命中率 85%,磁盘缓存大小控制在 50MB 以内
技术栈HandlerThread + LruCache + DiskLruCache + ThreadPoolExecutor(FixedThreadPool 4核心)
核心难点图片解码与缩放放在子线程且及时通知主线程更新;防止大量下载任务撑爆队列;生命周期自动取消
面试高频追问为什么用 HandlerThread 而不是普通 Thread?如何处理重复请求?LruCache 和 DiskLruCache 的淘汰策略?
可迁移回答话术“我们通过 HandlerThread 封装了一个后台循环,把图片解码任务 post 到该线程,解码完成后通过 Handler 发送消息到主线程。使用 FixedThreadPool 控制并发数,避免几百张图片同时下载导致内存飙升。”
💡 在回答项目时,主动带出 “Handler + Looper 实现线程间通信” 和 “线程池参数调优” 两个知识点,展示对消息机制的深度理解。