likes
comments
collection
share

面试串讲006-Handler的其他问题(1)

作者站长头像
站长
· 阅读数 15

前文中,我们基本了解了Handler与Looper,Looper循环的休眠和唤醒等知识,接下来我们继续上文来了解Handler的其他问题。

为什么主线程可以直接new Handler

从前文中,我们知道子线程使用Handler之前需要创建Looper并开启消息循环,那么主线程为什么不需要呢?

主要是在框架层实现中已经替我们提前创建Looper并开启Looper循环了,在ActivityThread.java的main函数中,代码如下:

面试串讲006-Handler的其他问题(1)

Message有哪些创建方式?消息池是什么知道吗?

通常情况下,我们可以通过Message的构造函数创建Message对象,当然我们也可以通过Message.obtain方法创建对象,通过Message.obtain可以从消息池中获取一个重用的Message对象,从而提升性能,Message.obtain实现如下:

 public static final Object sPoolSync = new Object();
 // 消息池实现
 private static Message sPool;
 private static int sPoolSize = 0;
 ​
 private static final int MAX_POOL_SIZE = 50;
 ​
 private static boolean gCheckRecycle = true;
 ​
 // 从全局的消息池中获取一个新的可用消息对象,消息池为空
 // 则返回新对象
 public static Message obtain() {
     synchronized (sPoolSync) {
         if (sPool != null) {
             Message m = sPool;
             sPool = m.next;
             m.next = null;
             m.flags = 0; // clear in-use flag
             sPoolSize--;
             return m;
         }
     }
     return new Message();
 }

可以看到消息池实际上是一个静态的Message头节点,按照链表的数据形式组织,最多能容纳50条消息,进程内共享。

那么消息是如何添加到消息池的呢?当我们调用Message.recycle的时候,就会将当前消息添加到消息池,代码如下:

 public void recycle() {
     if (isInUse()) {
         if (gCheckRecycle) {
             throw new IllegalStateException("This message cannot be recycled because it "
                     + "is still in use.");
         }
         return;
     }
     recycleUnchecked();
 }
 ​
 @UnsupportedAppUsage
 void recycleUnchecked() {
     // Mark the message as in use while it remains in the recycled object pool.
     // Clear out all other details.
     flags = FLAG_IN_USE;
     what = 0;
     arg1 = 0;
     arg2 = 0;
     obj = null;
     replyTo = null;
     sendingUid = UID_NONE;
     workSourceUid = UID_NONE;
     when = 0;
     target = null;
     callback = null;
     data = null;
 ​
     synchronized (sPoolSync) {
         if (sPoolSize < MAX_POOL_SIZE) {
             next = sPool;
             sPool = this;
             sPoolSize++;
         }
     }
 }

什么是epoll?Looper关联的epoll涉及几个fd?

epoll是一种多路IO复用技术,多路指的是可以关联多个文件描述符(File Descriptor),主要涉及思想是收集进程需要关注的文件描述符,当这些文件描述符中的一个或多个准备好IO时,函数返回并告知进程哪些文件描述符准备好了,此时进程只需操作准备好的文件描述符就好。

在Android中,eventfd经常与epoll组合使用,eventfd不同于pipe,其在底层创建一个虚拟文件,基于该虚拟文件实现了一个计数器的能力。Looper底层就是借助epoll+eventfd实现Looper循环的阻塞和唤醒的。

epollfd和eventfd的创建

跟踪Looper创建流程,在Looper.cpp的构造函数中,进行了mWakeEventFd的初始化,代码如下所示:

面试串讲006-Handler的其他问题(1)

随后调用rebuildEpollLocked进行epoll的初始化,代码如下:

面试串讲006-Handler的其他问题(1)

至此,当mWakeEventFd内容变更时,epoll机制就能发挥唤醒和阻塞的作用了。

Looper循环的阻塞

跟踪MessageQueue.nativePollOnce的实现,可以看到其最终调用的是Looper.cpp的pollInner方法,在该方法中epoll_wait等待mWakeEventFd准备好或超时时间达到时,才能正常继续向下执行,否则则阻塞在epoll_wait处,代码如下:

面试串讲006-Handler的其他问题(1)

Looper循环的唤醒

跟踪MessageQueue.enqueueMessage中调用的nativeWake方法,可以看到其最终调用Looper.cpp的wake方法,代码如下:

面试串讲006-Handler的其他问题(1)

从代码中可以看出,wake方法向eventfd中写入了unit64_t类型的数值1,此时epoll监听到eventfd准备完成,epoll_wait正常返回,唤醒Looper循环处理消息队列中的消息。

综上,一个Looper关联两个fd,epollfd和eventfd,整体的唤醒和阻塞流程如下图所示:

面试串讲006-Handler的其他问题(1)