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
:
mQueue
Handler
机制的消息队列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
四个成员变量的赋值。
Looper
Looper
如果来自于主线程,则不需要通过构造方法赋值;如果来自于工作线程,需要先执行Looper.prepare
初始化Looper
使用环境,并作为参数传递到构造方法中。MessageQueue
Looper
值确定后,消息队列即为其成员变量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 | xmt 005:~/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
会一直阻塞,直到有消息进来,更新这个值。 - 阻塞状态
mBlocked
next
中的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 gTLSKey
gTLSKey
共享变量。pthread_key_create
pthread_key_create(& gTLSKey, threadDestructor);
,绑定gTLSKey
变量,该变量所有线程都可以访问,但各个线程可以根据自己的需要往gTLSKey
中填入不同的值。threadDestructor
是销毁函数,线程退出时会调用该销毁函数。pthread_setspecific
pthread_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.enqueueMessage
Looper
从MessageQueue
中取出Message
后,指派给Handler.handleMessage
处理,处理完后回收Message.recycleUnchecked
Looper.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