想象一个忙碌的安卓公司(你的App)。这家公司高效运转的核心秘密,就在于一套精巧的"消息快递系统"(Handler/Looper机制)。它确保了任务(消息)能在正确的员工(线程)手上,以正确的顺序和时间得到处理。
1. 公司架构与核心部门:线程与消息队列
-
故事: 公司有两种员工(线程):
-
有"收件箱"的员工: 这类员工(如主线程
main
、专门处理后台任务的HandlerThread
)都有一个专属的"收件箱"(MessageQueue
- 消息队列)。他们:- 循环工作: 上班就是不断地检查收件箱(
Looper.loop()
)。 - 有活就干: 收到新邮件(
Message
)就处理。 - 没活就睡: 收件箱空了,就进入节能睡眠状态(线程阻塞在
epoll_wait
或nativePollOnce
),等待新邮件唤醒。
- 循环工作: 上班就是不断地检查收件箱(
-
临时工: 这类员工(普通的
java.lang.Thread
)没有收件箱。他们接到一个一次性任务(Runnable
)就埋头干,干完活就直接下班(线程结束)。
-
-
技术核心 (线程与消息的关系):
- 主线程 (
main
) 天生自带收件箱和循环处理机制。 - 普通线程需要显式创建消息系统:
Looper.prepare()
->new Handler()
->Looper.loop()
。 HandlerThread
是Android提供的"自带收件箱的后台员工",开箱即用。
- 主线程 (
2. 组建"收件部":消息队列创建 (Looper.prepare)
-
故事: 当一个员工(线程)想要拥有收件箱处理循环任务时,他需要组建自己的"收件部":
-
申请办公位 (Looper.prepare/prepareMainLooper):
-
prepareMainLooper()
: 这是给CEO(主线程)特批的豪华独立收件部。 -
prepare()
: 这是给普通员工申请的标准收件部。 -
关键代码 (
Looper.prepare()
):csharpjava Copy public static void prepare() { prepare(true); // 创建一个新的Looper,并允许退出 } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); // 核心!为当前线程创建唯一的Looper实例 }
-
-
配置基础设施 (new Looper -> new MessageQueue -> nativeInit):
-
new Looper()
: 任命一位"邮件分拣员"(Looper
)。 -
new MessageQueue()
: 给分拣员配一个"实体收件箱"(MessageQueue
)。 -
nativeInit()
: 这是关键!在"地下"(Native层/C++)建立更高效的通信管道。-
new NativeMessageQueue()
: 创建本地层收件箱。 -
new Looper(false)
: 创建本地层分拣员。 -
pipe()
: 建立一条"内部通讯管道"。这条管道有两个口:- 写端 (
mWakeWritePipeFd
): 用来"按铃"通知有新的紧急邮件(新消息到来)。 - 读端 (
mWakeReadPipeFd
): 分拣员(Looper
)用来监听"铃声"。
- 写端 (
-
epoll
: 这是分拣员的"智能监控系统"。epoll_create()
: 创建监控器。epoll_ctl()
: 把"管道读端"和公司其他重要的"通知渠道"(如触摸屏事件输入通道)都登记到监控器上。epoll_wait()
: 分拣员坐在这里,监控所有登记点。没有邮件/通知时,分拣员就在这里"睡觉"(线程阻塞),节省CPU。一旦监控点有动静(新消息或输入事件),epoll_wait
就立刻唤醒他。
-
-
-
-
技术核心 (为什么要用pipe和epoll):
- 高效唤醒:
pipe
提供了一种跨线程(甚至跨进程)的高效唤醒机制。Handler发送消息时写管道,Looper在epoll_wait
上被唤醒。 - 多路复用:
epoll
允许Looper同时监听多个事件源(消息管道、输入事件等),一个监控点搞定所有,避免轮询浪费资源。这是比select
/poll
更高效的选择。
- 高效唤醒:
3. 邮件分拣员的工作:消息循环 (Looper.loop)
-
故事: 收件员(
Looper
)的日常工作就是循环执行:-
检查收件箱 (
MessageQueue.next()
): 看看有没有邮件(Message
)。 -
智能等待 (
nativePollOnce()
):- 如果收件箱空了,分拣员就去"智能监控室"(调用
nativePollOnce()
->NativeMessageQueue.pollOnce()
->Looper.pollOnce()
->pollInner()
)。 - 在监控室,他坐在
epoll_wait()
椅子上睡觉(阻塞)。这是线程休眠的关键点!
- 如果收件箱空了,分拣员就去"智能监控室"(调用
-
被唤醒: 当有人往管道写端"按铃"(新消息到来)或有其他监控事件发生,
epoll_wait()
就会唤醒他。 -
取出邮件: 分拣员醒来后,从收件箱取出一封邮件(
Message
)。 -
派送邮件 (
msg.target.dispatchMessage(msg)
): 分拣员把邮件交给邮件上指定的"快递员"(Handler
,也就是msg.target
)去投递处理。 -
循环往复: 继续检查收件箱,周而复始。
-
-
关键代码 (
Looper.loop()
核心循环):javajava Copy public static void loop() { final Looper me = myLooper(); // 拿到当前线程的Looper final MessageQueue queue = me.mQueue; // 拿到消息队列 for (;;) { // 无限循环 Message msg = queue.next(); // 取出下一条消息 (可能会阻塞在next()里的nativePollOnce) if (msg == null) { // 只有Looper被quit()时才会返回null return; } msg.target.dispatchMessage(msg); // 关键!交给关联的Handler处理 msg.recycleUnchecked(); // 处理完,回收消息对象 } }
4. 发送邮件:消息发送 (Handler)
-
故事: 公司的任何员工(任何线程)想给某个"有收件箱的员工"派任务,就找对应的"快递员"(
Handler
)。-
创建任务单 (Message/Runnable): 你要么写一封详细的邮件(
Message
),包含what
(任务类型),arg1/arg2
(简单参数),obj
(复杂对象) 等;要么直接给个任务指令(Runnable
),Handler
会帮你打包成邮件。 -
选择快递员 (Handler): 每个快递员都绑定到一个特定员工的收件部 (一个线程的
Looper
)。 -
发送任务单 (sendMessage/post):
handler.sendMessage(msg)
handler.post(runnable)
(内部会封装成Message
)handler.sendMessageDelayed(msg, delayMillis)
(延迟发送)handler.sendMessageAtTime(msg, uptimeMillis)
(定时发送)
-
快递员投递 (Handler.enqueueMessage -> MessageQueue.enqueueMessage):
- 快递员把邮件投递到目标员工的收件箱(
MessageQueue
)。 - 收件箱会根据邮件设定的送达时间 (
when
) 进行排序插入。这就是实现延迟消息和定时消息的原理!
- 快递员把邮件投递到目标员工的收件箱(
-
叫醒分拣员 (
nativeWake
):- 如果目标员工的分拣员正在"智能监控室"睡觉(阻塞在
nativePollOnce
),新邮件插入队列后,快递员会猛敲一下管道写端(write(mWakeWritePipeFd)
),触发Looper.wake()
!这就是唤醒阻塞线程的关键!epoll_wait
监听到管道读端有数据,Looper立刻被唤醒去处理新消息。
- 如果目标员工的分拣员正在"智能监控室"睡觉(阻塞在
-
-
关键代码 (Handler发送 & MessageQueue入队):
arduinojava Copy // Handler发送消息的核心路径 public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) delayMillis = 0; return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; // Handler关联的MessageQueue return enqueueMessage(queue, msg, uptimeMillis); // 入队 } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; // 重要!标记这条消息由哪个Handler处理 return queue.enqueueMessage(msg, uptimeMillis); // 最终调用MessageQueue的入队方法 } // MessageQueue.enqueueMessage (简化) boolean enqueueMessage(Message msg, long when) { synchronized (this) { // ... (按when时间排序插入队列) if (needWake) { // 判断是否需要唤醒阻塞的Looper nativeWake(mPtr); // 关键Native调用!敲管道写端唤醒Looper } } return true; }
5. 处理邮件:消息处理 (Handler.dispatchMessage)
-
故事: 分拣员把邮件交给了快递员(
Handler
),快递员负责拆封执行:-
拆包策略 (
Handler.dispatchMessage
):- 如果邮件里直接装着任务指令 (
msg.callback
- 即Runnable
),立刻执行这个指令。 - 如果快递员自己有默认的任务处理手册 (
mCallback
-Handler.Callback
接口),把邮件交给手册处理。 - 如果以上都没有,就使用快递员自带的通用处理方法 (
handleMessage(Message msg)
),程序员通常在这里写处理逻辑。
- 如果邮件里直接装着任务指令 (
-
-
关键代码 (
Handler.dispatchMessage
):scssjava Copy public void dispatchMessage(Message msg) { if (msg.callback != null) { // 1. 优先执行Message自带的Runnable handleCallback(msg); } else { if (mCallback != null) { // 2. 其次交给Handler的Callback接口处理 if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); // 3. 最后调用子类实现的handleMessage方法 (程序员通常重写这里) } }
6. 邮件系统的实际应用:AsyncTask & HandlerThread
-
问题:CEO(主线程)太忙了!
- 主线程身兼数职:处理用户点击、更新UI、执行业务逻辑...任务太重!
- ANR风险: 如果主线程处理某个邮件(消息)时间过长(如网络请求),用户界面就会卡死,系统会弹出ANR对话框("应用无响应")。文中列出了各种超时限制(5s, 10s, 20s等)。
-
解决方案:委派任务!
-
方案A:雇佣专业后台员工 - HandlerThread
-
故事: 创建一个自带收件箱的后台员工 (
HandlerThread
)。主线程把耗时任务(如数据库操作、文件读写)打包成邮件(Runnable
),通过快递员(Handler
)发送给这个后台员工的收件箱。 -
代码:
javajava Copy HandlerThread handlerThread = new HandlerThread("MyBackgroundWorker"); handlerThread.start(); // 启动线程 (内部调用了Looper.prepare()和loop()) Handler bgHandler = new Handler(handlerThread.getLooper()); // 绑定到这个后台Looper bgHandler.post(new Runnable() { // 发送耗时任务 @Override public void run() { // 在后台线程执行耗时操作... // 需要更新UI?不行!要发回主线程处理 } }); // 退出时记得关闭 handlerThread.quit();
-
-
方案B:使用"跨部门协作工单" - AsyncTask
-
故事:
AsyncTask
像一个预定义好的工单模板。程序员填写:doInBackground()
: 在哪个后台部门执行耗时任务(实际在SerialExecutor
或THREAD_POOL_EXECUTOR
线程池)。onProgressUpdate()
: 任务进度如何反馈给CEO(主线程)。onPostExecute()
: 任务结果如何交给CEO(主线程)处理。
-
内部机制:
AsyncTask
内部维护了一个后台线程池和一个绑定到主线程的快递员 (InternalHandler
)。后台任务执行中、进度更新、结果返回,都通过这个快递员向主线程发送特定邮件(MESSAGE_POST_RESULT
,MESSAGE_POST_PROGRESS
)。 -
代码:
typescriptjava Copy new AsyncTask<Void, Integer, String>() { @Override protected String doInBackground(Void... voids) { // 在后台线程执行耗时操作 int progress = 0; while (progress < 100) { // ...工作... progress += 10; publishProgress(progress); // 发送进度邮件给主线程 (内部用Handler发MESSAGE_POST_PROGRESS) } return "Done!"; } @Override protected void onProgressUpdate(Integer... values) { // 在主线程更新进度条 (UI操作) progressBar.setProgress(values[0]); } @Override protected void onPostExecute(String result) { // 在主线程处理结果 (UI操作) textView.setText(result); } }.execute(); // 提交工单
-
-
总结:Android消息机制的精髓
- 解耦与通信:
Handler
是线程间通信的桥梁,发送方(任何线程)无需知道接收方(目标线程)的具体状态,只需通过Handler
投递Message
或Runnable
。 - 有序处理:
MessageQueue
保证了任务按when
(送达时间)顺序被Looper
取出处理,实现了延迟、定时和顺序执行。 - 高效休眠:
Looper
利用pipe
+epoll
(在Native层Looper
) 实现高效阻塞 (nativePollOnce
) 和精准唤醒 (nativeWake
),避免轮询浪费CPU。 - 主线程保护: 通过
HandlerThread
和AsyncTask
将耗时任务移出主线程,保证了UI的流畅性,避免了ANR。 - 框架基础: 整个Android UI框架(事件分发、View绘制、生命周期回调)都构建在这个消息循环机制之上。理解它,是深入理解Android系统运行原理的关键。
这篇文档详细剖析了这套"公司邮件系统"从创建(Looper.prepare
)、运转(Looper.loop
)、发送(Handler
)、接收(MessageQueue
)到高效通信(pipe/epoll
)的每一个齿轮是如何咬合的,以及如何利用这套系统(HandlerThread
/AsyncTask
)构建健壮、高效的应用程序。