故事:《安卓公司的消息快递系统》

想象一个忙碌的安卓公司(你的App)。这家公司高效运转的核心秘密,就在于一套精巧的"消息快递系统"(Handler/Looper机制)。它确保了任务(消息)能在正确的员工(线程)手上,以正确的顺序和时间得到处理。

​1. 公司架构与核心部门:线程与消息队列​

  • ​故事:​​ 公司有两种员工(线程):

    • ​有"收件箱"的员工:​ ​ 这类员工(如主线程main、专门处理后台任务的HandlerThread)都有一个专属的"收件箱"(MessageQueue - 消息队列)。他们:

      • ​循环工作:​ 上班就是不断地检查收件箱(Looper.loop())。
      • ​有活就干:​ 收到新邮件(Message)就处理。
      • ​没活就睡:​ 收件箱空了,就进入节能睡眠状态(线程阻塞在 epoll_waitnativePollOnce),等待新邮件唤醒。
    • ​临时工:​ ​ 这类员工(普通的java.lang.Thread)没有收件箱。他们接到一个一次性任务(Runnable)就埋头干,干完活就直接下班(线程结束)。

  • ​技术核心 (线程与消息的关系):​

    • 主线程 (main) 天生自带收件箱和循环处理机制。
    • 普通线程需要显式创建消息系统:Looper.prepare() -> new Handler() -> Looper.loop()
    • HandlerThread 是Android提供的"自带收件箱的后台员工",开箱即用。

​2. 组建"收件部":消息队列创建 (Looper.prepare)​

  • ​故事:​​ 当一个员工(线程)想要拥有收件箱处理循环任务时,他需要组建自己的"收件部":

    1. ​申请办公位 (Looper.prepare/prepareMainLooper):​

      • prepareMainLooper(): 这是给CEO(主线程)特批的豪华独立收件部。

      • prepare(): 这是给普通员工申请的标准收件部。

      • ​关键代码 (Looper.prepare()):​

        csharp 复制代码
        java
        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实例
        }
    2. ​配置基础设施 (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)的日常工作就是循环执行:

    1. ​检查收件箱 (MessageQueue.next()):​ ​ 看看有没有邮件(Message)。

    2. ​智能等待 (nativePollOnce()):​

      • 如果收件箱空了,分拣员就去"智能监控室"(调用 nativePollOnce() -> NativeMessageQueue.pollOnce() -> Looper.pollOnce() -> pollInner())。
      • 在监控室,他坐在 epoll_wait() 椅子上睡觉(阻塞)。​这是线程休眠的关键点!​
    3. ​被唤醒:​ ​ 当有人往管道写端"按铃"(新消息到来)或有其他监控事件发生,epoll_wait() 就会唤醒他。

    4. ​取出邮件:​ ​ 分拣员醒来后,从收件箱取出一封邮件(Message)。

    5. ​派送邮件 (msg.target.dispatchMessage(msg)):​ ​ 分拣员把邮件交给邮件上指定的"快递员"(Handler,也就是 msg.target)去投递处理。

    6. ​循环往复:​​ 继续检查收件箱,周而复始。

  • ​关键代码 (Looper.loop() 核心循环):​

    java 复制代码
    java
    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入队):​

    arduino 复制代码
    java
    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):​

      1. 如果邮件里直接装着任务指令 (msg.callback - 即 Runnable),立刻执行这个指令。
      2. 如果快递员自己有默认的任务处理手册 (mCallback - Handler.Callback 接口),把邮件交给手册处理。
      3. 如果以上都没有,就使用快递员自带的通用处理方法 (handleMessage(Message msg)),程序员通常在这里写处理逻辑。
  • ​关键代码 (Handler.dispatchMessage):​

    scss 复制代码
    java
    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)发送给这个后台员工的收件箱。

      • ​代码:​

        java 复制代码
        java
        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(): 在哪个后台部门执行耗时任务(实际在SerialExecutorTHREAD_POOL_EXECUTOR线程池)。
        • onProgressUpdate(): 任务进度如何反馈给CEO(主线程)。
        • onPostExecute(): 任务结果如何交给CEO(主线程)处理。
      • ​内部机制:​AsyncTask 内部维护了一个后台线程池和一个绑定到主线程的快递员 (InternalHandler)。后台任务执行中、进度更新、结果返回,都通过这个快递员向主线程发送特定邮件(MESSAGE_POST_RESULT, MESSAGE_POST_PROGRESS)。

      • ​代码:​

        typescript 复制代码
        java
        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消息机制的精髓​

  1. ​解耦与通信:​ Handler 是线程间通信的桥梁,发送方(任何线程)无需知道接收方(目标线程)的具体状态,只需通过 Handler 投递 MessageRunnable
  2. ​有序处理:​ MessageQueue 保证了任务按when(送达时间)顺序被 Looper 取出处理,实现了延迟、定时和顺序执行。
  3. ​高效休眠:​ Looper 利用 pipe + epoll (在Native层 Looper) 实现高效阻塞 (nativePollOnce) 和精准唤醒 (nativeWake),避免轮询浪费CPU。
  4. ​主线程保护:​ 通过 HandlerThreadAsyncTask 将耗时任务移出主线程,保证了UI的流畅性,避免了ANR。
  5. ​框架基础:​ 整个Android UI框架(事件分发、View绘制、生命周期回调)都构建在这个消息循环机制之上。理解它,是深入理解Android系统运行原理的关键。

这篇文档详细剖析了这套"公司邮件系统"从创建(Looper.prepare)、运转(Looper.loop)、发送(Handler)、接收(MessageQueue)到高效通信(pipe/epoll)的每一个齿轮是如何咬合的,以及如何利用这套系统(HandlerThread/AsyncTask)构建健壮、高效的应用程序。

相关推荐
敲代码的剑缘一心1 小时前
手把手教你学会写 Gradle 插件
android·gradle
青蛙娃娃1 小时前
漫画Android:动画是如何实现的?
android·android studio
aningxiaoxixi2 小时前
android 之 CALL
android
用户2018792831673 小时前
Android 核心大管家 ActivityManagerService (AMS)
android
春马与夏4 小时前
Android自动化AirScript
android·运维·自动化
键盘歌唱家5 小时前
mysql索引失效
android·数据库·mysql
webbin6 小时前
Compose @Immutable注解
android·android jetpack
无知的前端6 小时前
Flutter开发,GetX框架路由相关详细示例
android·flutter·ios
玲小珑6 小时前
Auto.js 入门指南(十二)网络请求与数据交互
android·前端
webbin6 小时前
Compose 副作用
android·android jetpack