Android源码:Handler, Looper和MessageQueue实现解析

做了6年的Android开发,此间做的事情非常杂包括ROM,SDK和APP,从来没有好好的研究过Android的基础代码。趁着这段时间项目没那么忙,把基础的东西仔细研究清楚并记录下来。

想到要分析Looper和Handler的时候其实正在看背光调节模块的代码,其中看到PowerManagerService中很多API最终是向Handler发送Message的形式进行实际操作。Handler在做App的时候用的很多,最常见的用法就是extend一个Handler类然后重载其handleMessage方法,然后要发消息的时候调用其sendMessage API即可。使用Handler还可以在不同的线程之间传递消息实现异步操作,下边我们扒一扒Android N的源码看看其实现以及Handler, Looper以及Thread之间的关系。

一,Looper, Thread之间的关系

先来看一段代码

0?wx_fmt=png

运行之后h1正常创建,但是创建h2的时候crash了:

0?wx_fmt=png

出错日志提示不能在一个没有调用过Looper.prepare()的Thread里边new Handler()。为什么Looper.prepare()会影响Handler的创建呢,先看源码哪里会抛出这个异常:

0?wx_fmt=png

这个sThreadLocal是一个ThreadLocal(线程局部)对象,它有get()/set()这么一对方法。它的工作机制是,仅当一个线程调用了它的set()来设置值之后,get()才能获取到设置进去的对象,而且这个对象是线程唯一的。也就是说在ThreadA里面调用了sThreadLocal.set(),在ThreadB里面调用sThreadLocal.get()还是null。所以在Looper.prepare()里一定调用了set():

0?wx_fmt=png

为什么主线程上来就有Looper而我们自己new的Thread就没有呢,因为在主线程里边有这段(代码):

0?wx_fmt=png

所以主线程是“生来”就有一个Looper跟它绑定在一起的,而且这个looper身份很特殊,它是整个进程里面的main looper,通过Looper.getMainLooper()就能拿到,接着就可以为所欲为的做些更新UI的活儿了。

那么其他的Thread要怎样才能也拥有一个Looper么,Android为我们提供了一个很好的例子 - HandlerThread类,用法如下:

HandlerThread ht = new HandlerThread("worker");
ht.start();
Handler handler = new Handler(ht.getLooper());

关键就在HandlerThread的run里面:

 
 

从上述分析可知Looper.prepare()之后这个Thread就有了一个跟它关联在一起的Looper了,这里头有两个需要注意的点:一是new Handler()要在ht.start()之后,二是如果要extend HandlerThread则记得要在run()里面调用super.run()。

二, Looper, Handler和MessageQueue

还是看Handler的构造函数

0?wx_fmt=png

可知它们的关系为,一个HandlerThread带有一个Looper,Looper有一个MessageQueue和多个与之关联的Handler,而一个Handler只与一个Looper有关联。UML类图如下:

0?wx_fmt=png

在使用中接触得最多的是Message,它在MessageQueue里面以单项链表的形式组织,mMessages指向链表头部。每个Java层的MessageQueue都有一个C++实现的NativeMessageQueue与之对应,关键的实现部分其实在native层中,下边的章节会分析。

三,工作机制

先借一张《Efficient Android Threading》里面的图:

0?wx_fmt=png

顾名思义,Looper的主要执行过程是无线循环的loop(),它在HandlerThread的run()运行起来:

0?wx_fmt=png

Handler的dispatchMessage()就没什么可说的了:

0?wx_fmt=png

里面要么就是执行msg自己定义的callback,要么就是在extend Handler时定义的handleMessage方法。要记得的就是这个方法的调用是在Handler关联的Looper所关联的线程里面执行的。所以如果你的Handler关联的是main Looper,就不要在handleMessage()里面做耗时的操作(网络请求,IO,密集计算等)了,否则很有可能造成ANR。

接着分析最关键的queue.next():

0?wx_fmt=png

捎带讲讲barrier的插入/拔出,没兴趣的可以略过毕竟用得少:

0?wx_fmt=png

四, native层的NativeMessageQueue机制

native looper使用了linux的epoll机制:

0?wx_fmt=png

这里最最重要的就是epoll_wait()调用,在linux shell里面man epoll_wait得到提示:

NAME
       epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor

SYNOPSIS       #include <sys/epoll.h>

       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
... ...
RETURN VALUE
       When successful, epoll_wait() returns the number of file descriptors ready for the requested I/O, or zero if no file  descrip‐
       tor  became  ready  during the requested timeout milliseconds.  When an error occurs, epoll_wait() returns -1 and errno is set
       appropriately.

其中timeout即Looper中的timeoutMillis即Java层的MessageQueue.next()中的nextPollTimeoutMillis。重新看next()中nextPollTimeoutMillis有3种可能的选择:0, -1或(int) Math.min(msg.when - now, Integer.MAX_VALUE);即最近一个msg的到期时间,传到epoll_wait()中产生的效果分表是马上返回、一直阻塞直至mEpollFd可用、阻塞msg.when-now的时长或者mEpollFd可用。这样就达到了闲时线程阻塞交出CPU,msg到期之后Thread接着执行的效果。当Thread正在永久阻塞的时候,还可以通过让mEpollFd变为可用来通知线程跳出阻塞。

五,Native Looper的核心 - epoll

还是只关心阻塞和唤醒那部分,首先要先了解linux的epoll机制,epoll需要三个linux系统调用包括上边的epoll_wait还有epoll_create以及epoll_ctl,看看native Looper是使用这些系统调用的:

0?wx_fmt=png

至此,mEpollFd和mWakeEventFd就变成了一对好基友,当向mWakeEventFd写入数据的时候 阻塞在epoll_wait(mEpollFd...);的线程将会被唤醒(向mWakeEventFd写入event的话epoll_wait还能读取到该event,我们只关心唤醒所以不作进一步解析)。所以native Looper的wake()是这样实现的:

0?wx_fmt=png

想要了解更多可以man epoll_ctl, man epoll_create以及查找linux epoll,管道pipe的实现机制。总的来说,Android的Looper设计的是相当的精巧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值