本文目录:
一、管道概述
二、Handler为何使用管道?
三、Handler为何采用管道而非Binder?
四、Android 6.0及以后的机制
在深入系统的学习Handler的时候,我们接触到了Looper之所以死循环不会导致CPU使用率过高,是因为使用了Linux下的pipe和epoll机制。
Android的应用层通过Message.java实现队列,利用管道和epoll机制实现线程状态的管理,配合起来实现了Android主线程的消息队列模型。
一、管道概述
管道,其本质是也是文件,但又和普通的文件会有所不同:管道缓冲区大小一般为1页,即4K字节。管道分为读端和写端,读端负责从管道拿数据,当数据为空时则阻塞;写端向管道写数据,当管道缓存区满时则阻塞。
在Handler机制中,Looper.loop方法会不断循环处理Message,其中消息的获取是通过 Message msg = queue.next(); 方法获取下一条消息。该方法中会调用nativePollOnce()方法,这便是一个native方法,再通过JNI调用进入Native层,在Native层的代码中便采用了管道机制。
二、Handler为何使用管道?
我们可能会好奇,既然是同一个进程间的线程通信,为何需要管道呢?
我们知道线程之间内存共享,通过Handler通信,消息池的内容并不需要从一个线程拷贝到另一个线程,因为两线程可使用的内存时同一个区域,都有权直接访问,当然也存在线程私有区域ThreadLocal(这里不涉及)。即然不需要拷贝内存,那管道是何作用呢?
Handler机制中管道作用就是当一个线程A准备好Message,并放入消息池,这时需要通知另一个线程B去处理这个消息。线程A向管道的写端写入数据1(对于老的Android版本是写入字符W
),管道有数据便会唤醒线程B去处理消息。管道主要工作是用于通知另一个线程的,这便是最核心的作用。
这里我们通过两张图来展示Handler在Java层和在Native层的逻辑:
Java层:
Native层:
三、Handler为何采用管道而非Binder?
handler不采用Binder,并非binder完成不了这个功能,而是太浪费CPU和内存资源了。因为Binder采用C/S架构,一般用于不同进程间的通信。
- 从内存角度:通信过程中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官方为何对此机制进行调整。