Android中线程间通信机制Handler疑问解答

本文详细探讨了Android中Handler的线程间通信机制,包括消息循环是否阻塞主线程、为何阻塞主线程、主线程阻塞机制、避免ANR的原因、线程数量、ThreadLocal的作用及其实现机制,以及消息处理的不同方式。通过对Handler的理解,揭示了Android应用进程的运行原理。

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

Android中线程间通信机制Handler疑问解答

说起Handler的使用,几乎是开发者入门必备的开发技能。而且都会知道Handler配合一个Looper和MessageQueue来实现消息的创建、分发、处理。

每一个Handler会绑定到创建它的线程以及一个消息队列。

通过Handler,我们可以跟其他线程发消息实现线程切换,也可以给当前线程Handler发消息实现定时任务。

这里总结一下对Handler一些可能有的疑问。

消息循环时是否会阻塞主线程

当Looper开始循环后,会开启一个死循环,在这个循环中不停的通过next()方法从MessageQueue中读取消息,而MessageQueue的next()是一个可能会被阻塞的方法。

public static void loop() {
   final Looper me = myLooper();
   if (me == null) {
       throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
   }
   final MessageQueue queue = me.mQueue;

   for (;;) {
       //当读取下一条消息,该循环可能会被next()方法阻塞住,直到读取到消息。
       //如果消息为空,说明线程已退出,该循环也可以直接跳出了。
       Message msg = queue.next(); // might block
       if (msg == null) {
           // No message indicates that the message queue is quitting.
           return;
       }

        ...省略部分代码
    }
}

那么MessageQueue的next()是如何在哪阻塞的呢?
答案是 nativePollOnce(ptr, nextPollTimeoutMillis);
这是一个本地C++实现的方法,ptr是native层NativeMessageQueue的指针,nextPollTimeoutMillis是一次阻塞的超时时间。
如果nextPollTimeoutMillis为0,则无需阻塞直接返回。
如果nextPollTimeoutMillis为-1,表示需要无限阻塞,直到被唤醒。
如果nextPollTimeoutMillis为其他正整数,则会阻塞相应的时间,然后返回。

Message next() {
    final long ptr = mPtr;
    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
           final long now = SystemClock.uptimeMillis();
           Message prevMsg = null;
           //寻找下一条合法的消息
           Message msg = mMessages;
           //如果msg.target为null,代表是异步消息,要一直找到下一条同步消息
           if (msg != null && msg.target == null) {
               do {
                   prevMsg = msg;
                   msg = msg.next;
               } while (msg != null && !msg.isAsynchronous());
           }
           //如果找到一条待处理的消息,有两种情况需要考虑
           //1. 此消息的执行时间还未到,需要阻塞一段时间
           //2. 此消息的执行时间已到,则返回
           if (msg != null) {
               if (now < msg.when) {
                   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;
           }
    }
}

注:消息在入队列时,会按照消息的执行时间点排序,所以查询到的第一条消息,如果还没有到执行的时间点没有到,那么后面的消息都不应该处理。

为什么要阻塞主线程

防止主线程退出

我们都知道一个线程是CPU最小可执行单元,当线程中代码执行完成后,该线程就会退出销毁。而Android中的主线程负责UI绘制、事件响应等,如果主线程退出了,应用就无法继续运行下去了。那么保持一个线程一直执行的方法就是无限循环。
所以当主线程没有消息需要处理时,也必须阻塞住,不能退出。

主线程阻塞机制

Linux epoll

本地方法nativePollOnce(ptr, nextPollTimeoutMillis)的核心实现就是epoll命令。
下面看epoll在Android中应用:

//system/core/libutils/Looper.cpp

//构造唤醒事件的fd
mWakeEventFd = eventfd(0, E
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值