在深入系统的学习Handler的时候,我们接触到了Looper之所以死循环不会导致CPU使用率过高,是因为使用了Linux下的pipe和epoll机制。
Android的应用层通过Message.java实现队列,利用管道和epoll机制实现线程状态的管理,配合起来实现了Android主线程的消息队列模型。
对Handler,我们在之前整理了如下内容,也上对Handler的机制有了相当程度的了解:
Android Handler 机制(一):Handler 运行机制完整梳理
Android Handler 机制(二):Hander 机制深入探究问题梳理
同时为了更进一步的了解Handler机制,我们整理了Epoll机制的相关基本知识:
本文我们讲述一下Android的Handler机制为何使用管道。
一、管道概述
管道,其本质是也是文件,但又和普通的文件会有所不同:管道缓冲区大小一般为1页,即4K字节。管道分为读端和写端,读端负责从管道拿数据,当数据为空时则阻塞;写端向管道写数据,当管道缓存区满时则阻塞。
在Handler机制中,Looper.loop方法会不断循环处理Message,其中消息的获取是通过 Message msg = queue.next(); 方法获取下一条消息。该方法中会调用nativePollOnce()方法,这便是一个native方法,再通过JNI调用进入Native层,在Native层的代码中便采用了管道机制。
二、Handler为何使用管道?
我们可能会好奇,既然是同一个进程间的线程通信,为何需要管道呢?
我们知道线程之间内存共享,通过Handler通信,消息池的内容并不需要从一个线程拷贝到另一个线程,因为两线程可使用的内存时同一个区域,都有权直接访问,当然也存在线程私有区域ThreadLocal(这里不涉及)。即然不需要拷贝内存,那管道是何作用呢?
Native层:
三、Handler为何采用管道而非Binder?
- 从内存角度:通信过程中Binder还涉及一次内存拷贝,handler机制中的Message根本不需要拷贝,本身就是在同一个内存。Handler需要的仅仅是告诉另一个线程数据有了。
- 从CPU角度,为了Binder通信底层驱动还需要为何一个binder线程池,每次通信涉及binder线程的创建和内存分配等比较浪费CPU资源。
从上面的角度分析可得,Binder用于进程间通信,而Handler消息机制用于同进程的线程间通信,Handler不宜采用Binder。
四、Android 6.0及以后的机制
在Android 6.0及以前的版本使用管道与epoll来完成Looper的休眠与唤醒的。
在Android 6.0及以后的新版本中使用的是eventfd与epoll来完成Looper的休眠与唤醒的。
感兴趣的可以进一步的学习和了解管道的知识及eventfd的知识,并比较一下两种机制的优劣,进而明白Android官方为何对此机制进行调整。