Handler中loop方法为什么不会导致线程卡死

本文深入探讨了Android中Handler的循环机制,解释了为什么Handler的loop方法不会导致线程卡死。通过分析源码,详细说明了Looper、MessageQueue和IdleHandler的工作原理,以及它们如何协同工作确保线程流畅运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近闲的时间比较多,浏览一些公众号和一些IT网站,突然看到一个问题:

Handler中loop方法为什么不会导致线程卡死?

我先浏览了一下源码,以下为截取部分片段:

/**Looper*/
public static void loop() {
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            msg.recycleUnchecked();
        }
    }

/**MessageQuene*/
Message next() {
        ...
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
               ...
            }
            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

仔细一看,这确实是两个死循环,难道死循环不会阻塞了吗,科技都这么进步了吗?,果断尝试:

/**Activity*/
runOnUiThread {
     while (true) {
          Log.d("lzp", "while")
     }
}

还是卡死了,页面一篇惨白,那为啥Handler中的loop不会卡死线程?

正文

为了解决这个问题,我开始在网上搜索相关的资料,但是都不能完全解决我的疑问,果然解决问题还得靠自己。

创建一台模拟器,选定Android系统版本,打上断点,问题就明朗了。

小建议:如果想要看源码,建议使用模拟器,这样断点能对得上源码,国产真机不推荐,具体原因大家都懂。

问题一:Handler中的loop真的是死循环吗?

了解过源码的朋友都知道,Handler与Looper、Messagequene的关系,这里就简要说一下:

Handler:消息处理器;
Looper:消息管理器;
MessageQuene:消息队列;

我们要理解的是Looper中的looper方法,仔细分析looper源码,其实它只完成几步工作:

public static void loop() {
       	// 从队列去除消息
        final MessageQueue queue = me.mQueue;
        // for死循环
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            // 消息分发  
            msg.target.dispatchMessage(msg);        
			// 消息回收
        	msg.recycleUnchecked();
       }
}

很尴尬,从上面的代码,我们得出的结论只有loop方法确实是个死循环。但是我们也找到了非常有价值的线索:

// 请注意注释:可能会锁住
Message msg = queue.next(); // might block

赶紧去看一眼MessageQuene.next方法:

 Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 下一次循环的时间
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			// 此native方法很可疑
            nativePollOnce(ptr, nextPollTimeoutMillis);
			// 同步锁
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 找到要取出的消息
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                // 消息不为空,直接返回msg
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        // 不要锁住
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages. 消息为空
                    nextPollTimeoutMillis = -1;
                }

                // 判断是否有空闲时执行的任务
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 如果没有,直接锁住
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    // 记录是否要锁住
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

           	// 开始执行Hanlder空闲时的任务
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
				// 如果这个任务不需要维持下去,会删除这个任务
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

上面的代码有点长,但是每一句都有很重要的意义,这里直接总结一下我的发现:

  • 每次遍历Msg,都会计算下一次的遍历时间,而这个时间作为参数传给了nativePollOnce;
  • nativePollOnce会计算下一次的唤醒时间,如果是-1,表示锁住,等待唤醒;
  • IdleHandler,表示空闲时执行的任务,即需要锁住线程的时候;

我还从没使用过IdleHandler,所以就尝试了一下:

// 版本判断
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        handler.looper.queue.addIdleHandler {
            Log.d("lzp", "Handler in Idle")
            // 返回true,表示该任务每次空闲时都会执行
            // 返回false,表示该任务只会执行一次
            true
        }
}

经过测试,IdleHandler并不可靠,下面举例:

创建线程,每50毫秒向Handler插入一条Msg,10秒后结束,我希望IdleHandler执行;

很遗憾,上面的需求并不会得到满足,因为程序的执行是非常快的,我们刚处理完第一条Msg,第二条还没有进来,这个时候就已经进入到空闲状态,同理接下来每一条Msg都有可能执行IdleHandler。

问题二:MessageQuene的同步锁何时唤醒

我们已经了解了Handler的轮询处理虽然的确是死循环,但是内部有同步锁的机制,所以并不会锁死线程,但是MessageQuene的同步锁又是什么时候被唤醒的呢?

仔细思考,我们可以猜测:应该是在新消息插入的时候,唤醒了同步锁。我们跟踪一下Handler.post:

// 最后会跟踪到MessageQuene.enqueueMessage
 boolean enqueueMessage(Message msg, long when) {
 		...
        synchronized (this) {
            ...
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            // 是否需要唤醒同步锁
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                // 立刻唤醒同步锁
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    // 异步消息不做处理
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // 唤醒同步锁
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

Handler.postXXX方法最终都会执行到MessageQuene.enqueueMessage。

总结

最后做一个总结:

  • Handler中的Looper和MessageQuene虽然是双死循环,但是MessageQuene中的死循环中使用了同步锁机制,避免资源的占用,所以并不会锁住线程;
  • 当有新消息进入队列时,会唤醒同步锁;

以上内容仅供学习参考,如有错误欢迎指正。

<think>我们被要求详细解释Android中的Handler机制工作原理。根据用户提供的引用内容,我们可以整合出一个全面的解释。Handler机制Android中用于线程间通信的重要机制,尤其常用于子线程执行任务后更新UI(因为UI操作必须在主线程进行)。核心组件:1.Handler:负责发送和处理消息。2.Message:消息的载体,包含需要传递的数据。3.MessageQueue:消息队列,用于存储Handler发送的消息(实际上是一个按时间排序的优先级队列)。4.Looper:循环取出MessageQueue中的消息,并分发给对应的Handler处理。5.ThreadLocal:用于每个线程存储自己的Looper,实现线程隔离。工作流程:1.首先,在需要创建Handler线程中,必须先调用Looper.prepare()(主线程在启动时已经初始化了Looper,所以不需要手动调用)。2.Looper.prepare()会创建一个Looper对象,并将其存储到当前线程的ThreadLocal中。同时,Looper内部会创建一个MessageQueue。3.然后调用Looper.loop(),开始循环从MessageQueue中取出消息。4.创建Handler对象,Handler在创建时会绑定当前线程Looper(通过ThreadLocal获取)以及Looper中的MessageQueue。5.当我们在其他线程中调用Handler的sendMessage()方法时,Handler会将Message放入其绑定的MessageQueue中。6.Looper在循环中不断从MessageQueue中取出消息(如果队列为空则阻塞),然后调用该消息的target(即发送该消息的Handler)的dispatchMessage()方法。7.Handler的dispatchMessage()方法会调用handleMessage()方法,在该方法中处理消息(通常在这里更新UI)。注意点:-一个线程只能有一个Looper,但可以有多个Handler,这些Handler都绑定同一个Looper(也就是同一个线程Looper)。-主线程(UI线程)在启动时已经调用了Looper.prepareMainLooper()和Looper.loop(),所以我们可以在主线程直接创建Handler。-在子线程中创建Handler,必须先调用Looper.prepare(),然后调用Looper.loop(),否则会报错。源码层面关键点(参考引用[3]和[4]):-Handler发送消息最终会调用到MessageQueue的enqueueMessage方法,将消息插入队列。-Looper.loop()方法中有一个死循环,不断调用MessageQueue的next()方法获取消息,然后通过msg.target.dispatchMessage(msg)分发消息。-消息处理完毕后,系统会回收消息(Message有消息池机制,避免频繁创建对象)。数学描述:假设消息队列为$Q$,消息入队操作$enqueue(Q,m)$,出队操作$dequeue(Q)$。Looper循环可表示为:$$\text{while}(m=dequeue(Q))\neq\text{null}\text{do}\text{process}(m)$$其中$m$为消息,$process(m)$即处理消息。下面我们按步骤详细解释。</think>### Android Handler机制工作原理详解 Handler机制Android线程间通信的核心框架,主要用于后台线程执行任务后更新UI。其工作原理可分为以下关键组件和流程: --- #### 核心组件与关系 ```mermaid graph TD A[Handler] -->|发送消息| B(MessageQueue) B -->|存储消息| C[Message] D[Looper] -->|循环取出| B D -->|分发消息| A E[ThreadLocal] -->|存储Looper| D ``` 1. **Handler** - 作用:发送消息(`sendMessage()`)和处理消息(`handleMessage()`) - 关键特性:绑定到创建它的线程Looper - 发送消息流程: ```java handler.sendMessage(msg) → MessageQueue.enqueueMessage() → Looper.loop() → handler.dispatchMessage() ``` 2. **MessageQueue** - 数据结构:基于时间优先级的单链表(`Message`对象链表) - 核心方法: - `enqueueMessage()`:插入消息(按`when`时间排序) - `next()`:阻塞式获取下一条消息(当队列空时进入休眠) 3. **Looper** - 作用:消息循环泵,不断从MessageQueue取出消息分发 - 关键方法: - `prepare()`:初始化当前线程Looper(存入ThreadLocal) - `loop()`:启动消息循环(死循环调用`queue.next()`) - 主线程Looper初始化: ```java // ActivityThread.main() Looper.prepareMainLooper(); Looper.loop(); ``` 4. **ThreadLocal** - 作用:线程私有数据存储,确保每个线程有独立的Looper - 实现原理:每个线程的`Thread.threadLocals`字段存储专属数据 --- #### 完整工作流程 1. **消息发送** - 子线程调用`handler.sendMessage(msg)` - Handler将消息插入关联的MessageQueue: ```java // Handler.java public boolean sendMessage(Message msg) { return enqueueMessage(queue, msg, uptimeMillis); } ``` 2. **消息存储** - MessageQueue按`msg.when`(触发时间)排序插入链表 - 若队列空或新消息最早,唤醒Looper线程 3. **消息轮询** - Looper.loop() 循环调用`Message msg = queue.next()` - `next()`方法逻辑: - 有消息 → 立即返回 - 无消息 → 调用`nativePollOnce()`进入休眠(Linux epoll机制) 4. **消息分发** - Looper调用`msg.target.dispatchMessage(msg)` - Handler处理消息的优先级: ```mermaid graph LR A[Message.callback] -->|Runnable对象| B B[Handler.mCallback] -->|Callback接口| C C[handleMessage] -->|默认处理| ``` 5. **资源回收** - 消息处理完成后调用`msg.recycleUnchecked()` - Message加入对象池复用(避免频繁GC) --- #### 数学建模 消息队列操作可表示为: $$ \text{dequeue}(Q) = \begin{cases} \text{msg}_i & \text{if } \exists \text{msg}_i \text{ where } t_{\text{msg}_i} \leq t_{\text{now}} \\ \text{wait}(\Delta t) & \text{otherwise} \end{cases} $$ 其中: - $Q$:消息队列 - $t_{\text{msg}_i}$:消息触发时间 - $\Delta t$ = $ \min(t_{\text{msg}_j}) - t_{\text{now}} $ --- #### 关键设计思想 1. **线程隔离** 通过ThreadLocal实现各线程独立的Looper,避免并发冲突[^2]。 2. **阻塞唤醒优化** 使用Linux epoll机制实现空闲时CPU休眠,节省资源[^4]。 3. **对象复用机制** Message池(sPool)减少对象创建开销(类似享元模式)。 4. **线程切换封装** 开发者无需直接操作线程,通过Handler自动切换执行线程。 > 示例:子线程更新UI > ```java > new Thread(() -> { > Message msg = handler.obtainMessage(); > msg.obj = "Data from background"; > handler.sendMessage(msg); // 自动切换到主线程处理 > }).start(); > ``` --- #### 常见问题解析 **为什么线程不会Looper.loop()卡死?** - 消息处理是分段执行的,每条消息处理时间必须短(否则ANR) - 空闲时调用`nativePollOnce()`释放CPU资源 **内存泄漏场景**: 匿名Handler持有外部Activity引用 → 解决方案: ```java // 使用静态内部类 + WeakReference static class SafeHandler extends Handler { WeakReference<Activity> ref; SafeHandler(Activity activity) { ref = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { Activity activity = ref.get(); if (activity != null) { /* 更新UI */ } } } ``` Handler机制通过以上设计,实现了高效、安全的跨线程通信,是Android系统流畅运行的基础[^1][^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值