[Java] 不依赖Android环境的Java Handler

最近项目中遇到将android project转成java project的需求,要把项目中android相关的部分去掉。其中最纠结的就是如何替换handler。
handler多用于指定线程(Looper)上执行回调,还可以当成一个消息队列使用。通过sendMessageDelayed可以做到事件的延时执行、条件执行和去抖(debouncing)。
于是,我利用Java里面的ScheduledExecutorService写了一个简单的Handler,接口与原Handler基本一致,代码如下所示:

Message类:

Looper类:

Handler类:

使用方法如下所示:

[Android] Handler源码解析 (Native层)

接前文[Android] Handler源码解析 (Java层),接下来对Handler机制在Native层上作解析。

Java层的MessageQueue中有4个native方法:

下面分别进行介绍。

nativeInit()和nativeDestroy(long ptr)

nativeInit()在MessageQueue初始化时被调用,返回一个long值,保存在mPtr中。

nativeInit()的实现在/frameworks/base/core/jni/android_os_MessageQueue.cpp中:

该JNI方法新建了一个NativeMessageQueue对象,然后将其指针用reinterpret_cast为long并返回给java层。同样地:

nativeDestory()方法中,将long型的ptr转换为NativeMessageQueue指针,然后再销毁对象。

NativeMessageQueue对象初始化的代码如下所示:

可以看到初始化方法中对mLooper进行了赋值。留意到Looper::getForThread();一句,结合其下的代码,猜想这是类似ThreadLocal模式的应用。接下来看看Looper类。

Looper类的声明在/system/core/include/utils/中,实现在/system/core/libutils/中,先来看一下Looper类的初始化方法:

从上面可以看出,Looper对象中维护着两个描述符,分别用于读和写。其中读描述符注册到epoll中。合理猜想looper的夸进程的睡眠和唤醒机制是通过epoll实现的。目标线程在读描述符mWakeReadPipeFd上等待,其他线程往mWakeWritePipeFd写入数据时,即可通过epoll机制将目标线程唤醒。

nativePollOnce(long ptr, int timeoutMillis)和nativeWake(long ptr)

nativePollOnce和nativeWake方法的实现如下所示:

可见这两个方法只是对Looper类的pollOnce和wake方法的简单封装。先看一下Looper对象的pollOnce方法实现如下所示:

先不管什么mResponses、outFd、outEvents和outData,我们先来看一下pollInner的实现。pollInner实现比较复杂,这里只看对本文有用的部分:

awoken()的实现代码如下所示:

awoken()只是简单地读出wake()在mWakeWritePipeFd上写入的数据。Looper对象的wake方法实现如下所示:

正如前面所述,往mWakeWritePipeFd写数据即可唤醒在mWakeReadPipeFd上等待的线程。

总结

综上,在native层,一次wait/wake过程简述如下:

  1. native层Looper对象初始化时,新建了一个匿名管道,并将读管道(mWakeReadPipeFd)注册到epoll上。
  2. pollOnce方法调用pollInner方法,其中epoll_wait方法在mWakeReadPipeFd上等待读取。(wait
  3. wake方法被调用,往写管道(mWakeWritePipeFd)上写入字符“W”。
  4. pollInner方法继续执行,调用awoken从mWakeReadPipeFd读出数据。(wake

可画出框架图如下所示:

[Android] Handler源码解析 (Java层)

*** 之前写过一篇文章,概述了Android应用程序消息处理机制。本文在此文基础上,在源码级别上展开进行概述***

简单用例

Handler的使用方法如下所示:

或者:

又或者:

源码解析

首先看其构造函数:

由此引入了两个关键对象Looper和MessageQueue。

Looper

先看 mLooper = Looper.myLooper(); 这一句发生了什么:

可以看到,该方法返回一个sThreadLocal对象中保存的Looper。关于ThreadLocal类,请参考这里,本文不展开。
如果尚未在当前线程上运行过Looper.prepare()的话,myLooper会返回null。接下来看看Looper.prepare()的实现:

可以看到该方法只是简单地新建了一个Looper对象,并将其保存在sThreadLocal中。接下来看一下Looper的构造函数。

调用完Looper.prepare()后,需调用Looper.loop()才能使消息循环运作起来,其源码如下所示:

可以简单地将Looper.loop()理解成一个不断检测message queue是否有数据,若有即取出并执行回调的死循环。 接下来看一下Message类:

what、arg1、arg2这些属性本文不作介绍,我们把目光集中在next、sPoolSync、sPool、sPoolSize这四个静态属性上。

当我们调用Message.obtain()时,返回了一个Message对象。Message对象使用完毕后,调用recycle()方法将其回收。其中obtain方法的代码如下所示:

可以看到,obtain方法被调用时,首先检测sPool对象是否为空,若否则将其当做新的message对象返回,并“指向”message对象的next属性,sPoolSize自减。可以看出message对象通过next属性串成了一个链表,sPool为“头指针”。再来看看recycle方法的实现:

如果message对象不是处于正在被使用的状态,则会被回收。其属性全部恢复到原始状态后,放在了链表的头部。sPool对象“指向”它,sPoolSize自增。

综上可以看出,通过obtain和recycle方法可以重用message对象。通过操作next、sPoolSync、sPool、sPoolSize这四个属性,实现了一个类似栈的对象池。

msg.target为handler类型,即向handler成员的dispatchMessage方法传入msg参数,其实现如下所示:

这里可以看到回调了各种接口。

到目前为止,我们知道了如何处理在消息队列里面的msg对象,但仍不知道msg对象是如何放到消息队列里面的。通常来说,我们通过Handler的sendMessage(msg)方法来发送消息,其源码如下所示:

可知sendMessage最终会调用queue.enqueueMessage(msg, uptimeMillis)将msg对象保存至message queue中,uptimeMillis表示msg执行回调的时刻。 我们来看一下MessageQueue类的enqueueMessage方法:

结合注释,我们可以了解到msg push到queue中时,queue的状态的变化和处理队列的逻辑。

前文中Looper对象的loop方法中:

可以看出,message queue的next方法被调用时,可能会发生堵塞。我们来看一看message queue的next方法:

代码执行流程见注释。其中IdleHandler是一个接口:

IdleHandler提供了一个在MessageQueue进入idle时的一个hook point。更多时与barrier机制一起使用,使message queue遇到barrier时产生一个回调。

总结

前面涉及到的几个主要的类Handler、Looper、MessageQueue和Message的关系如下所述:
1. Handler负责将Looper绑定到线程,初始化Looper和提供对外API。
2. Looper负责消息循环和操作MessageQueue对象。
3. MessageQueue实现了一个堵塞队列。
4. Message是一次业务中所有参数的载体。

框架图如下所示:

最后,留意到MessageQueue中有4个native方法:

将会在后续文章中进行介绍。