Handler 机制是 Android 系统处理同一进程不同线程间通信的机制,基于 Linux 系统的 epoll 机制实现。
Android 两大机制:Binder 机制用于处理进程间通信;Handler 机制用于处理进程内的线程间通信,线程间交互。
概念
Message
意为消息,发送到Handler进行处理的对象,携带描述信息和任意数据。MessageQueue
意为消息队列,Message的集合。Looper
消息泵,用来从MessageQueue中抽取Message,发送给Handler进行处理。Handler
处理Looper抽取出来的Message。
也就是说所有的消息处理都是串行执行的。
他们之间的关系:
- 每个
Thread只对应一个Looper - 每个
Looper只对应一个MessageQueue - 每个
MessageQueue中有N个Message - 每个
Message最多指定一个Handler来处理事件 - 每个
Thread可以对应多个Handler
在如下操作中都是基于 UI 主线程,在异步任务中使用 Handler 机制更新 UI 必须用 new Handler(); 来初始化。
1 | // 默认使用 UI 主线程的 Looper |
源码目录及类对应文件
源码目录结构
1 | // Framework Java |
类或接口对应文件
1 | Message.java: Message |
Message 详解
Message 是 Handler 机制中的数据容器,和 Binder 机制中的 Parcel 功能一样。Message 数据结构特点:
- 链表
Message是单链表数据结构,成员变量next保存下一条消息,而当前消息是链表的头结点。Message中的消息池Message sPool利用链表头结点特性实现栈,快速存取消息;MessageQueue中的成员变量Message mMessages利用链表结构实现先进先出队列,确保先进入的消息先被处理。 - 存储简单数据
成员变量arg1, arg2用来存储简单整型数据。 - 存储复杂数据
成员变量Object obj, Bundle data用来存储复杂数据,其中Bundle能够存放键值对。
源码分析
1 | public final class Message implements Parcelable { |
快速查看 Message API 。
消息池 sPool
Message 中使用了单向链表结构的消息池,sPool 总是指向链表顶部,所以这个链表模拟的是栈结构,即消息池的数据结构为链表实现的栈。
- 消息池的创建
源码中可以看出,消息在被回收后recycleUnchecked(),将当前消息加入到消息池链表中,即消息池才开始有可以重复利用的Message;创建消息池使用了synchronized关键字,确保线程池的操作是同步的。如果系统中,同时有很多消息在被传递,当一部分消息使用结束后都会被回收,此时消息池会积蓄的越来越多。 - 消息的重复利用
创建消息时推荐使用Message.obtain()方式,因为每次obtain时,都会判断消息池中是否有可以循环利用的消息,如果存在则取出并清空消息标记,否则才新建一个消息。
实例化一个消息,请使用
Message.obtain(),充分利用消息池循环利用的特点。
重要成员变量
long when
消息投递的绝对时间(投递时当前时间+设置的延迟时间),在Handler发送消息或发布任务时,指定具体的值。MessageQueue在消息加入队列时,会根据when值决定消息在队列中的顺序。Handler target
保存处理该消息的Handler,在Handler.enqueueMessage()中将Message和对应处理该消息的Handler关联起来。Runnable callback
在Handler.java的分析中得出结论:消息的处理有三个优先级,可以直接使用Message.callback来进行消息处理。
消息标识 flags
成员变量 flags 有如下四个值:
- 0
消息标识清除。表示该消息被创建new Message()、或者是从消息池中新取出的Message.obtain()。可以理解是一个新消息,能修改当前消息内容。 FLAG_IN_USE
消息正在被使用。表示消息进入了消息池栈sPool中、或者进入了消息队列MessageQueue中。在消息池中,表示被回收了的消息正在被消息池管理,可以被取出循环利用;在消息队列中,表示消息被投递,正等待被取出处理。FLAG_ASYNCHRONOUS
表示异步消息;消息默认都是同步的,只能在Message和Handler构造方法中,特别指定为异步消息。如果设置了同步屏障,异步消息优先级将高于同步消息;消息队列MessageQueue.next()在取出消息时,遇到同步屏障会暂停所有的同步消息,将异步消息取出并处理,直到移除同步屏障。所以异步消息通常和同步屏障配合使用,同步屏障详细分析见MessageQueue中的分析。FLAGS_TO_CLEAR_ON_COPY_FROM
它的值和FLAG_IN_USE一样,只有在copyFrom()方法中会用到。
Messenger 相关
Message 包含具体的数据信息,Messenger 是一名信差,用于进程间发送指定的 Message 。实质上 Messenger 使用 AIDL 和 Handler 机制来实现进程间的异步通信。详细参考Messenger 详解 。
Messenger replyTo
回复此消息的回调信差,跨进程时通信时,可以利用Message中携带的信差,完成消息回复。sendingUid
跨进程发送异步消息的进程ID。
Looper 详解
Looper 类设计原则:此类包含基于 MessageQueue 设置和管理事件循环所需的代码。影响队列状态的 API 应该在 MessageQueue, Handler 上定义,而不是在 Looper 本身上定义。例如在队列上定义 idle handlers 和 sync barriers,而在 Looper 上定义准备线程,循环和退出。Looper 类的主要功能:
- 提供线程上下文环境,创建与线程绑定的
Looper - 创建
MessageQueue loop循环从MessageQueue中获取下一条消息(若无消息线程阻塞等待),指派Handler处理消息并回收消息- 提供
loop循环退出方法
源码分析
Looper 类中大部分都是 static 的变量和方法,不能直接实例化,通常都是固定格式来初始化。
1 | public final class Looper { |
快速查看 Looper API 。
重要成员变量
sThreadLocal
结论:一个线程只能对应一个Looper。从源码中可以看到,静态变量sThreadLocal存储了所有线程对应的Looper,而ThreadLocal中数据存储的数据结构是一个定制的哈希表,其key值是当前线程。sMainLooper
当前应用主线程对应的Looper,在应用对应的主线程ActivityThread.main()中初始化。通常我们在Activity中获取到的主线程Looper对应的就是sMainLooper。
Looper 的初始化
代码中可以看到,Looper 的构造方法是私有的,客户端通常使用 Looper.prepare(...) 来实例化,并初始化应用场景。而 Looper.prepareMainLooper() 是用来初始化主线程环境的,整个 Android Framework 中只有 ActivityThread, SystemServer 这两个带有 main() 方法的类调用过,而这两个类分别用来开启应用主线程和系统主线程。Looper 的构造方法中,初始化了 mQueue 和 mThread :
mQueueHandler机制的消息队列MessageQueue就是在Looper的构造方法中实例化的。mThread
赋值的是当前线程:可以是主线程或者工作线程。
Looper.loop()
流程非常简单,进入无限循环并不停的从消息队列中获取消息,而获取消息的过程可能会阻塞。 Looper 线程拿到消息后,执行消息处理 Handler.dispatchMessage() 并回收已经处理过的消息 Message.recycleUnchecked() 。
Looper 退出
Looper 通常是通过 Looper.loop() 进入无线循环,从消息队列中取出消息并处理。Handler 机制为工作线程提供了退出方法 quit/quitSafely ,调用 MessageQueue.quit() 来结束并退出。只要调用了 quit/quitSafely,不管是正在退出还是已经退出,Looper 就不再接收新的消息。Handler 发送的消息,在 MessageQueue.enqueueMessage 中直接返回 false,不做任何处理。
主线程的
Looper是不允许退出的,在MessageQueue的构造方法中设定。prepareMainLooper调用的是prepare(false),即不能退出。
典型工作线程流程
Looper 线程典型的实现示例如下,将 prepare() 和 loop() 的分离,来创建一个与 Looper 通信的 Handler。
1 | class LooperThread extends Thread { |
Handler 详解
Handler 子类必须是 static 的,避免潜在的内存泄露,在构造方法中可以打开开关做检测。Handler 类将 Message, Looper, MessageQueue 串起来,给外部提供完整的接口,发送并处理消息。它管理的消息队列 MessageQueue 是 Looper 的成员变量;而 Looper 可以是主线程,也可以是工作线程,在 Handler 初始化时指定。主要功能包含:
- 初始化一个消息
- 发布任务或者发送消息
- 回到
Looper所在线程处理消息 - 移除消息或其回调
源码分析
1 | public class Handler { |
快速查看 Handler API 。
构造方法及成员变量
从 Handler 的构造方法可以看出来,实例化时主要对 Looper, MessageQueue, Callback, mAsynchronous 四个成员变量的赋值。
LooperLooper如果来自于主线程,则不需要通过构造方法赋值;如果来自于工作线程,需要先执行Looper.prepare初始化Looper使用环境,并作为参数传递到构造方法中。MessageQueueLooper值确定后,消息队列即为其成员变量mQueue。Callback
可以直接通过Callback处理消息事件;特别是不需要自定义Handler子类来重写handleMessage时,默认为null。mAsynchronous
是否允许异步执行,默认值为false即是同步执行的。它最终会被设置到对应的Message.flags = FLAG_ASYNCHRONOUS,在Handler中被没有其他作用。
自定义 Handler 的子类在实例化时,为了避免内存泄露,通常将子类设置为 static 类型,也就是避免内部类引用当前 Activity 对象。
生成消息
Handler 提供了生成消息的方法,但都是对 Message.obtain 的封装,所以在使用消息前,推荐直接使用 Message 类来生成。
任务发布
不管是通过 post***() 还是 send***() 方法来发布任务或者消息,最终都是调用的 enqueueMessage(),即将 Message 加入到消息队列中 MessageQueue 。enqueueMessage() 的两个重要动作:
Handler和Message的关联Message的成员变量target存储了处理该消息的Handler,在消息入队时,将两者关联起来。- 设置异步消息
Handler.mAsynchronous成员变量,在入队时将对应的Message设置为异步。
消息处理
dispatchMessage 是 Handler 消息处理的入口方法,有三个优先级来处理消息:
- 优先:如果
Message中定义了Callback - 其次:如果
Handler中定义了Callback - 最后:才是调用子类重写的
handleMessage
如果是自定义 Handler 子类,必须重写 handleMessage 来实现消息处理。而消息处理是在 Looper 所在线程来处理的,Looper 如果来自于主线程则在主线程中处理消息;如果来自于工作线程则在工作线程中处理消息。
移除消息
remove***() 系列的方法,是指移除 Message 或者 Message.callback;最终调用的是从 MessageQueue 中移除对应消息。
Messenger 相关
Handler 中实现了 Messenger 用于进程间异步通信的代码。源码中可以看出 Messenger 通过 AIDL 来实现的进程间通信的;而通过 Handler.sendMessage 来实现异步通信。详细参考Messenger 详解 。
BlockingRunnable 阻塞并同步执行任务
BlockingRunnable 同步等待指定任务执行完成才会返回,通过 Object.wait/notify 实现同步阻塞功能。Handler.runWithScissors 方法是具体的实现,它是一个 @hide 方法,客户端 App 并不能直接调用若。该方法是如果调用线程与 Looper 所在线程如果在同一个线程,直接执行完后退出;如果不是同一线程,可以简单的实现 timeout,方法调用以后会阻塞,直到传入的 runnable 结束或者是 timeout 。
MessageQueue 详解
MessageQueue 的内部存储了一组消息,以队列的形式对外提供插入和删除的工作。采用单链表的数据结构来存储消息列表,按照消息发生的绝对时间 Message.when 排序。所以消息队列并不是绝对的先进先出队列,是按照消息发生时间先进先出。Looper.loop 会进入无限循环,而它会调用 MessageQueue.next 方法取出下一条消息;当有消息时,取出交给 Handler 处理;当没有消息时,当前线程便会阻塞在 next() 方法的 nativePollOnce() 中,当线程便会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生时,唤醒当前线程;这里的线程休眠唤醒,涉及到 Linux epoll 机制,后面会详细介绍。MessageQueue 是在多线程环境下操作,所以绝大部分的方法都使用了 synchronized(this) 关键字;是 Handler 机制的核心类,native 代码在 android_os_MessageQueue.cpp 中实现;MessageQueue 还能监听文件描述符,当发生变化时做出相应事件处理,但是 Handler 机制中并没有使用该功能,本文不做介绍。MessageQueue 主要功能:
- 初始化
native环境 - 将
Message加入队列 - 调用
native方法,当前线程阻塞等待,直到从队列中取出Message - 多线程环境中维护消息队列入队、出队、销毁
源码分析
1 | public final class MessageQueue { |
快速查看 MessageQueue API
重要成员变量
mMessages
用于存储消息,利用Message链表结构实现先进先出队列,确保先进入或者时间最近的消息先被处理。ArrayList mIdleHandlers
记录消息队列中有多少个IdleHandler。IdleHandler[] mPendingIdleHandlers
由mIdleHandlers转换为对应的数组。mBlocked
当next方法在等待pollOnce,mBlocked记录next是否处于阻塞状态。mQuitting
记录当前消息队列是否正在退出。mNextBarrierToken
同步屏障令牌,同步屏障Barriers表示一个没有指定Handler的Message,它的arg1参数记录了这个令牌。mFileDescriptorRecords
存储被监听文件描述符的表,Handler机制中消息发送并没有使用文件描述符监功能,暂时不做介绍。AOSP系统源码中仅frameworks/base/core/java/android/os/ParcelFileDescriptor.java用到了。
初始化
Handler 机制中,MessageQueue 的实例化是在 Looper 的构造方法中实现的,赋值两个成员变量:
mQuitAllowed
是否运行消息队列退出。主线程的消息队列是不允许退出的,工作线程可以。mPtr
执行nativeInit方法,并将native层的NativeMessageQueue类对象地址赋值给Java层的成员变量mPtr。通过这种方式将Java层的对象与native层的对象关联在了一起。这种在Java层保存native层对象引用地址来实现关联的方式,在Android源代码中能经常看到。
同步屏障
同步屏障 SyncBarrier 是一个特殊的消息 Message ,有两个重要特点:
Handler target为空arg1记录同步屏障令牌
同步屏障消息的what为空,也就是不能通过what来标记消息了,而通过arg1记录同步令牌来做为消息标记。令牌非常重要,移除同步屏障时需要指定具体的令牌。
如果设置了同步屏障,消息队列所有的同步消息都会被暂停,next() 只会取出异步消息处理,直到移除同步屏障。如果没有正确移除同步屏障,整个消息队列将会挂起,同时也不再循环取出同步消息执行。
- 设置同步屏障
postSyncBarrier()设置同步屏障时,将它加入到消息队列,并将同步屏障消息设为头结点。也就是同步屏障是插入到队列的头部的,优先级很高,消息通过enqueueMessage加入队列都是插入队尾的。设置同步屏障并不会唤醒队列。 - 移除同步屏障
removeSyncBarrier(int token)根据令牌移除同步屏障,遍历消息队列。如果同步屏障位于头结点,且下一条消息为同步消息,表示队列没有被被挂起,唤醒队列。移除同步屏障后,消息队列恢复正常,循环取出消息。
同步屏障相关方法都是 @hide 的,也就是说只会在 Android Framework 内部使用,客户端 App 并不能应用这些功能。并且同步屏障和异步消息在 Android Framework 中使用的也很少,比如搜索到的应用如下:
1 | xmt005:~/android$ grep -irsn postSyncBarrier frameworks/ |
Android 系统的屏幕点击事件,就是一个异步消息;View 请求启动绘制生命周期 ViewRootImpl.scheduleTraversals 会设置同步屏障,优先处理异步消息。
消息入队 enqueueMessage
后台线程完成耗时操作后,通过对象 Handler.post***/send*** 发送消息,最终会进入到 MessageQueue.enqueueMessage ,而 enqueueMessage 仅仅是将消息按照 when 顺序插入到消息队列中。后台线程不会被阻塞,执行完毕可以退出。

- 入队顺序
Message.when
按照消息的绝对时间Message.when排序,插入到队列中,如果时间相同就按照入队先后顺序。确保消息投递的绝对时间when越小,越早出队。 - 唤醒队列
needWake
如果消息队列处于阻塞状态mBlocked=true时,插入消息时才需要唤醒队列。但是当消息队列队首为同步屏障,并且新消息本身为异步消息,同时当消息队列中还存在其他异步消息时,此时不会唤醒,具体看上面的流程图。消息插入消息队列,正常情况下不会唤醒,除了图中的几个特殊情况。 - 消息队列正在退出
mQuitting
如果消息队列调用了quit()方法,表示消息队列正在退出;当消息队列正在退出时,直接回收当前准备入队的消息,并返回false,入队失败。
消息队列存储顺序
消息队列中,首先按照 msg.when 排序,结合同步屏障,异步消息,后期补一个图。
消息出队 next
主线程的 Looper.loop() 会无限循环,不停的调用 MessageQueue.next() 获取下一条消息,交给 Handler 处理。而 next 通过 nativePollOnce 方法实现线程的阻塞,阻塞时间为消息的延迟时间。当时间到了后,唤醒主线程取出这个消息;即后台线程发布的消息,回到主线程来处理。

- 阻塞时间
nextPollTimeoutMillis
表示阻塞时间,通常为消息的延迟时间。如果为 0 ,表示不阻塞,直接返回;如果为 -1 ,表示一直阻塞。当消息队列中没有消息时,nextPollTimeoutMillis=-1使得next会一直阻塞,直到有消息进来,更新这个值。 - 阻塞状态
mBlockednext中的for循环取到消息后,next返回这个消息并退出,消息队列阻塞状态为false;for循环没有取到消息,如果存在IdleHandler时,利用这段时间处理空闲任务;否则设置消息队列为阻塞状态true;重新进入循环继续阻塞等待取消息。 - 消息出队顺序
同步消息都是按照msg.when即插入顺序先进先出的;当消息队列队首为同步屏障时,挂起队列中所有同步消息,循环取出队列中所有的异步消息,直到移除同步屏障。 - 消息队列正在退出
mQuitting
如果消息队列调用了quit()方法,表示消息队列正在退出;当消息队列正在退出时,调用dispose()方法,销毁native环境,跳出Looper.loop循环,退出整个机制。 - 空闲线程
IdleHandler
当消息队列为空,或者消息投递时间没到时;如果添加了空闲线程处理器IdleHandler,此时会利用线程空闲时间执行这些任务。
消息轮询状态
isPolling/isPollingLocked 方法主要是否返回当前线程轮询状态,即当前线程是否处于 epoll_wait 阻塞等待中,该方法在 Android Framework 中使用的也很少:
1 | frameworks/base/services/core/java/com/android/server/Watchdog.java:121: |
MessageQueue.mBlocked 也是记录的阻塞状态,和 isPolling/isPollingLocked 功能差不多,只是没有 isPolling/isPollingLocked 方法返回的状态更精细、更准确。
删除消息
- 删除指定消息
符合指定消息的可能并不止一个,所以需要遍历整个队列,找到这些符合条件的消息。删除时分两种情况:如果指定消息是头结点,或者头部几个节点都是指定消息,删除指定消息后,重新指定头结点;如果头结点或者新的头结点不是指定消息,依次遍历整个队列,找到目标消息移出队列并回收。 - 删除所有消息
从头结点开始遍历队列,移出队列中所有消息并删除。 - 删除延迟消息
从头结点开始遍历队列,对比当前时间和Message.when,找出延迟消息,删除掉这些还没来得及投递处理的消息。
消息队列退出
MessageQueue.quit() 方法中有如下几个判断:
- 是否运行退出
mQuitAllowed
主线程的Looper循环是不允许退出的,这在主线程初始化时,构造MessageQueue决定的;工作线程可以退出。 - 是否正在退出
mQuitting
因为是多线程环境,所以使用了synchronized关键字来确保并发正确。 - 是否安全移除
safe
是否安全移除,主要区别在于移除哪些消息。如果不安全退出,直接移除队列中所有消息;如果安全退出,对比当前时间和Message.when,只移除延迟消息。
MessageQueue.quit() 方法主要是移除消息队列中对应的消息,并设置了 mQuitting=true ;调用 nativeWake() 方法唤醒 next() 方法,如果是安全退出,则取出剩余的消息并返回给 Handler 处理;next 方法中如果取不到消息,当 mQuitting=true 时,释放资源并退出整个 Looper.loop() 循环机制。
IdleHandler 接口
一个回调接口,在消息队列阻塞等待前,当前线程可以利用这段时间做别的事情。
MessageQueue.addIdleHandler()
添加回调接口。MessageQueue.removeIdleHandler()
移除回调接口。- 示例
比如ActivityThread中,利用这个空闲时间,添加了GcIdle回收内存和Idler释放AMS相关资源。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48// 1.内存回收
Looper.myQueue().addIdleHandler(mGcIdler);
final class GcIdler implements MessageQueue.IdleHandler {
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
//Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" +
// Binder.getLastGcTime() + "m now=" + now);
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
//Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
BinderInternal.forceGc("bg");
}
}
// 2. AMS 释放相关资源,调用 activityIdle
Looper.myQueue().addIdleHandler(new Idler());
private class Idler implements MessageQueue.IdleHandler {
public final boolean queueIdle() {
ActivityClientRecord a = mNewActivities;
...
if (a != null) {
mNewActivities = null;
IActivityManager am = ActivityManagerNative.getDefault();
ActivityClientRecord prev;
do {
...
if (a.activity != null && !a.activity.mFinished) {
try {
am.activityIdle(a.token, a.createdConfig, stopProfiling);
a.createdConfig = null;
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
prev = a;
a = a.nextIdle;
prev.nextIdle = null;
} while (a != null);
}
...
}
}
native 方法
简要描述几个 native 方法的作用:
private native static long nativeInit();:初始化native对象,绑定Looper和线程等private native static void nativeDestroy(long ptr);销毁native对象private native void nativePollOnce(long ptr, int timeoutMillis);
唯一的非静态方法,也就是说这个方法是属于对象的;当前线程阻塞最多等待timeoutMillis时间(类似Object.wait(timeout)),这段时间内可能会被nativeWake唤醒。private native static void nativeWake(long ptr);唤醒nativePollOnce函数private native static boolean nativeIsPolling(long ptr);查询nativePollOnce是否正处于阻塞状态private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);添加文件描述符监听事件
Native JNI 层
Native JNI 层起到一个连接的作用,Handler 机制中使用的 epoll 等功能都是在系统层实现的,JNI 对这些功能调用做一个封装,相当于外观模式中的外观类。注意:Android Framework 中还有一个 ALooper 类,也是实现的消息机制,它主要是用于多媒体相关,代码路径 frameworks/av/media/ ,和本文介绍的消息机制没有任何关系。
如下根据 MessageQueue 中的 6 个 native 方法逐一展开分析,具体对应的 JNI 类为 Android_os_MessageQueue.cpp :
环境初始化 nativeInit
nativeInit 函数主要是初始化所有 native 需要使用的类对象,变量等,主要就是 Looper.cpp 相关资源的初始化;函数的返回值是一个 nativeMessageQueue 对象,它是对 Looper.cpp 相关功能的一个封装。
1 | static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { |
销毁 nativeDestroy
native 中的几个关键对象,都使用了智能指针管理,所以销毁时减少引用计数,如果引用不存在自动销毁。
1 | static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, |
轮询 nativePollOnce
唯一的非静态方法,也就是说这个方法是属于对象的,因为每个对象可以处理自己的回调。Handler 机制的核心,nativePollOnce 调用了 epoll 中的 epoll_wait 方法,当前线程阻塞等待:文件描述符事件(本文主要是唤醒事件)更新或者超时。阻塞等待时当前线程会释放 CPU 资源进入休眠状态,直到被唤醒,这个功能是 Linux epoll 机制实现。
1 | static void android_os_MessageQueue_nativePollOnce(JNIEnv* env,jobject obj, |
pollOnce 方法非常简单,需要获取当前对象,所以该方法不能为是静态的。调用了 Looper.pollOnce 方法,它会回调 pollObj 对象的 dispatchEvents 方法。而 Handler 收发消息机制中,并不会使用到文件描述符事件的回调,只需简单了解。
唤醒 nativeWake
Looper.cpp 中,唤醒事件是单独的文件描述符在监听,具体为 eventfd ,用于线程间通信。实际上 nativeWake 函数仅仅是向 eventfd 中写入一个值,将 epoll_wait 函数唤醒。
1 | static void android_os_MessageQueue_nativeWake(JNIEnv* env, |
是否正在轮询等待 nativeIsPolling
nativeIsPolling 函数返回的是 Looper.cpp 中 epoll_wait 是否处于阻塞等待状态;这个方法在 Android Framework 使用的也很少。
1 | static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, |
监听文件描述符 nativeSetFileDescriptorEvents
nativeSetFileDescriptorEvents 函数通过 Looper.cpp::addFd ,向 epoll_wait 中添加文件描述符监听事件,对应的回调 dispatchEvents 在 pollOnce 中分析过。
1 | static void android_os_MessageQueue_nativeSetFileDescriptorEvents( |
系统层实现
系统层 Looper.cpp 都是在 Linux 中编程实现,包括下面介绍的线程相关方法、eventfd 文件描述符、epoll 机制。Looper.cpp 实现了完整的 native 消息收发机制,使用 MessageEnvelope 存储消息;Request 存储文件描述符的监听;这些都是提供给 native 层使用,不过 Android Framework 使用的并不是特别多。
本文介绍的 Java 层 Handler 消息通信机制中,收发消息过程,只会有唤醒 POLL_WAKE 和超时 POLL_TIMEOUT 两种返回事件。最开始看源码时总是将 Java 消息和 native 的消息一一对应,或者相互关联;但实际上它们是两个完全不同的东西,只是共用了 eventfd, epoll 消息处理机制,本文不做 native 消息的介绍,这两个概念不要混淆了。
线程相关方法
TLS: Thread Local Storage 线程本地存储,和 Java ThreadLocal 功能一样,保存线程私有数据。使用同一个变量,不同线程存储和拿到的结果能够被隔离,相当于提供了一个同名而不同值的全局变量(这个全局变量相对于拥有这个变量的线程来说)。
pthread_key_t gTLSKeygTLSKey共享变量。pthread_key_createpthread_key_create(& gTLSKey, threadDestructor);,绑定gTLSKey变量,该变量所有线程都可以访问,但各个线程可以根据自己的需要往gTLSKey中填入不同的值。threadDestructor是销毁函数,线程退出时会调用该销毁函数。pthread_setspecificpthread_setspecific(gTLSKey, looper.get());,当前线程向gTLSKey中写入数据。pthread_getspecific(Looper*)pthread_getspecific(gTLSKey);,从gTLSKey中取出当前线程存储的值。pthread_once仅执行一次
函数声明:int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
函数功能:变量once_control的值设为PTHREAD_ONCE_INIT时,本函数保证init_routine()函数在进程执行序列中仅执行一次。
根据上面的简单介绍,Looper.cpp 中关于 TLS 线程本地存储的几个函数源码如下:
1 | // once_control 变量,即确保函数只执行一次 |
eventfd 文件描述符
eventfd 是一个用来通知事件的文件描述符,用来实现进程/线程间的等待/通知 wait/notify 机制以及数据交互,通过内核取唤醒用户态的事件。
只有一个系统调用接口:int eventfd(unsigned int initval, int flags);,返回一个新建的 eventfd 对象,该对象是一个内核维护的无符号的 64 位整型计数器,初始化值为 initval ;flags 有几个可选项:
EFD_NONBLOCK:设置对象为非阻塞状态,read读取eventfd时不会阻塞EFD_CLOEXEC:表示执行时关闭;设置此标志后,在执行exec时才关闭eventfd描述符,否则该描述符一直打开
eventfd 是一个文件描述符,所以直接通过 read/write/close 来读写关闭。常见示例为进程/线程 A 向 eventfd 中写入数据,进程/线程 B 从 eventfd 中读取数据。Looper.cpp 中源码分析:
1 | Looper::Looper(bool allowNonCallbacks) : |
epoll 机制
I/O 多路复用就通过一种机制,可以监视多个文件描述符,一旦某个文件描述符就绪(通常是读或者写就绪),能够通知程序进行相应的读写操作。select, poll, epoll 都是 IO 多路复用的机制,本质上它们都是同步 I/O,都需要在读写事件就绪后自己负责进行读写。epoll 是 select 和 poll 的增强版本;相对于 select, poll 来说,epoll 更加灵活,没有描述符限制。epoll 使用一个文件描述符可以监听多个其他多个描述符,将用户关系文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的 copy 只需一次。epoll 只有三个系统调用:
epoll_create
函数声明:int epoll_create(int size);,创建一个epoll的句柄。
参数size用来告诉内核这个监听的数目一共有多大。需要注意的是当创建好epoll句柄后,它就是会占用一个fd值,使用完后通过close(mEpollFd);来关闭。epoll_ctl
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);,事件注册函数,告诉内核要监听什么类型的事件以及哪些文件描述符。
参数epfd是epoll_create创建的句柄;fd是被监听文件描述符;op表示动作,用三个宏来表示:EPOLL_CTL_ADD:注册新的fd到epfd中;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd;
参数event告诉内核需要监听什么事,可以是以下几个宏的集合:EPOLLIN:表示对应的文件描述符可以读;EPOLLOUT:表示对应的文件描述符可以写;EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);EPOLLERR:表示对应的文件描述符发生错误;EPOLLHUP:表示对应的文件描述符被挂断;EPOLLET: 将EPOLL设为边缘触发Edge Triggered模式,这是相对于水平触发Level Triggered来说的,关于ET/LT主要是效率的区别,不做展开;EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听的话,需要再次加入到队列里;epoll_wait
函数声明:int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);,阻塞等待事件的产生。
参数events用来从内核得到事件的集合;maxevents告诉内核这个events有多大,不能大于创建epoll_create()时的size;参数timeout是超时时间(单位为毫秒,0 会不会阻塞立即返回,-1不确定或永久阻塞)。该函数返回需要处理的事件数目,如返回 0 表示已超时。
Looper.cpp 中 epoll 除了监听唤醒文件描述符 eventfd,还可以同时监听 MessageQueue 设置的文件描述符,通过 addFd 添加。部分源码解析:
1 | void Looper::rebuildEpollLocked() { |
从代码中可以看到,Handler 机制只监听了 mWakeEventFd 的可读事件,以及超时事件;mPolling 记录了 epoll_wait 是否处于阻塞等待,在 MessageQueue.isPolling 中查看该变量的值。
小结
Looper.cpp 中通过 eventfd 实现线程间通信;通过 epoll 实现监听 eventfd 文件描述符的读写事件,以及 Message 的超时事件。
消息收发流程图
Handler 机制大致流程:
Looper.prepare准备环境,初始化所有相关对象Looper.loop进入无限循环,阻塞等待获取下一条消息MessageQueue.next- 创建
Handler子类,初始化 - 创建
Message,存储相关信息 Handler.sendMessage发送消息,加入到消息队列MessageQueue.enqueueMessageLooper从MessageQueue中取出Message后,指派给Handler.handleMessage处理,处理完后回收Message.recycleUncheckedLooper.quit退出机制

HandlerThread 创建与销毁
在 Android 开发中经常会使用到线程,一想到线程,很多同学就立即使用 new Thread(){...}.start(); 这样的方式。这样如果在一个 Activity 中多次调用上面的代码,那么将创建多个匿名线程,程序运行的越久可能会越来越慢。因此,需要一个 Handler 来启动一个线程,以及删除一个线程,保证线程不会重复的创建。
使用 HandlerThread 和 Handler 配合实现异步后台任务。特点:
- 由 2 个
Handler和 1 个HandlerThread来实现 - 后台线程串行执行
源码分析
HandlerThread 是参考 Looper 标准用法实现的工作线程,
1 | public class HandlerThread extends Thread { |
HandlerThread 的创建
代码示例:
1 | // UI线程的Handler |
注意:mBackHandler 的初始化必须在 mBackThread.start(); 之后,否则拿不到这个线程的 looper。源码中可以看到,getLooper 会一直等待;而 Looper 是在 run 中创建的,并且通知等待线程。
这种模式通过 mBackHandler.post(new Runnable() {}) 来实现后台异步任务执行,所有后台任务都是通过 HandlerThread 这个线程执行的,但是 HandlerThread 是串行执行任务的,也就是每次 post 后进入队列排队执行。
HandlerThread的退出
1 |
|
在 Activity.onDestroy 退出时,等待 mBackThread 线程销毁完成再退出!
其他线程间通信机制
LocalBroadcastManager本质还是Handler机制EventBus
查看了EventBus源码,UI主线程间通信还是采用了 Handler 机制:public class HandlerPoster extends Handler implements Poster {...};后台线程间通信,采用的是Java标准的notify/wait机制。
后续
Messenger 信差
Messenger 用于进程间异步通信,其中通过 AIDL 来实现的进程间通信的;通过 Handler.sendMessage 来实现异步通信。详细参考Messenger 详解 。
OnFileDescriptorEventListener 监听文件描述符
MessageQueue.java 中提供了完整的文件描述符监听功能,包括 Android_os_MessageQueue.cpp, Looper.cpp 中也实现了对应的功能,本文暂不做描述。
Native Loop 机制
Java 层的 Handler 机制,在系统层的 Looper.cpp 文件中重新全部实现了一遍,供 Native 代码使用。本文仅仅介绍了其中的唤醒和轮询两个功能。而 Looper.cpp 中还包含添加文件描述符监听,收发 native 的消息。可以参考:Android Native层Looper详解 ,Android Native Looper机制 - 监听文件描述符 。
总结
Handler通过Linux系统调用的意义
本文介绍的Handler消息收发机制,完全可以使用Object.wait/notify来简单实现;但是源码为什么还使用了Linux epoll来实现呢?这是因为Framework除了提供等待和唤醒方法,还提供了其他文件描述符监听机制等,epoll机制更强大更高效。- 应用程序的主线程处于无限循环状态
应用主线程ActivityThread进入Looper.loop()后不允许退出,直到应用退出;也就是说主线程是无限循环的,通过epoll机制阻塞等待时释放CPU资源,直到超时或事件触发才唤醒主线程。而我们经常遇到的Activity的ANR限制是针对Activity等组件的,实际上并不是主线程。 Handler机制中的线程切换Handler机制通常有两个线程:主线程和后台工作线程。主线程loop无限循环阻塞等待;后台工作线程完成耗时任务,发送完消息后就可以退出了;此时阻塞等待的主线程收到epoll机制的唤醒,拿到这个消息直接处理。因为消息Message是共享变量,所以整个Handler机制使用了很多同步锁synchronized,所以两个线程之间仅仅只是等待/通知交互。Looper.cpp底层epoll机制并没有涉及数据交互,仅仅是线程间的通知什么时候能够取出消息,相当于Java notify/wait机制。而epoll还支持监听文件描述符涉及到数据交互,但是不属于本文介绍范围。Handler机制的退出
主线程是无法退出的,会一直循环等待,除非退出应用程序;其他线程的退出通过调用Looper.quit来退出,退出时会移除消息队列的所有消息,并设置mQuitting使得MessageQueue.next返回null,Looper.loop退出循环。- 消息队列的唤醒
消息队列MessageQueue有三个地方会唤醒:退出quit必须唤醒;移除同步屏障removeSyncBarrier和消息入队enqueueMessage满足条件时才唤醒。通过nativeWake触发唤醒。
参考文档
- gityuan:Android消息机制-Handler
- 隔壁老李头:Android Handler 机制系列
- Android Handler.java API
- Android Looper.java API
- Android MessageQueue.java API
- Android Message.java API
- Android Handler Looper机制
- Looper 探底
- Android消息机制 空闲消息处理器
- AMS 释放内存详解
- Android中Looper的quit方法和quitSafely方法
- Handler之同步屏障机制sync barrier
- Linux 本地线程存储栈-TLS
- Linux多线程学习 pthread_once
- Linux eventfd
- Linux IO多路复用之epoll总结
- Android源码解析之Handler机制详解-Java简化版本的实现
- Handler BlockingRunnable 介绍
- MessageQueue的队列管理
- 深入理解MessageQueue