Handler消息机制全面解析
1、消息机制的作用
Android的消息机制主要是指Handler的运行机制。Handler能够让你发送和处理消息,以及Runnable对象;每个Handler对象对应一个Thread和 Thread的消息队列。 当你创建一个Handler时,它就和Thread的消息队列绑定在一起,然后就可以传递消息和runnable对象到消息队列中, 执行消息后就从消息队列中退出。
Handler的作用就是:调度消息和runnable对象去被执行;使动作在不同的线程中被执行。
当一个应用程序中进程被创建时,它的主线程专门运行消息队列(MessageQueue),去管理顶层的应用程序相关的对象如:Activity,BroadcastReceiver,Windows等,你可以创建你的Thread,和主线程进行交互——通过Handler,交互的方法就是通过post或者sendMessage。但是在你的新线程中,给定的Message或者Runnable,会在适当的时候的被调度和处理。
2、消息机制基本构成
消息系统模型一般会包括以上七个部分(消息原型,消息队列,消息发送,消息循环,消息获取,消息派发,消息处理)。实际上的核心是消息队列和消息循环,其余部分都是围绕这两部分进行的。
在Android中将这七个部分抽象成四个独立的部分:Handler,Message,MessageQueue,Looper。
Message:就是消息原型,包含消息描述和数据,它是Handler接收与处理的消息对象,它可以在内部携带少量信息.可以传递bundle数据
MessageQueue:就是消息队列,存储消息对象的队列,每个线程只有一个阻塞队列
Looper:每个线程只能够有一个Looper,管理MessageQueue,负责循环读取MessageQueue中的消息交给Handler去处理。是每个线程中MessageQueue的管家,调用Looper.loop()方法后,就会进入到一个无限循环中,会从头开始取消息并传递到Handler的handlerMessage()方法中。
Handler:发送消息与处理消息,驾驭整个消息系统模型,统领Message,MessgeQueue和Looper。在其子线程或当前线程发送Message,在UI线程处理消息对象,发送的消息经过处理后最终在handlerMessage方法中处理。
3、Message
public final class Message implements Parcelable {
//用于区别消息的类型
public int what;
//携带数据,空消息所携带的数据
public int arg1;
public int arg2;
//携带数据(进程间通信时只能携带系统定义的parcelable对象,自定义的不行)
public Object obj;
//Messenger进行进程间通讯时,用于实现双向通讯
public Messenger replyTo;
//携带数据,可存放多条
Bundle data;
//消息所携带的代码语句数据
Runnable callback;
//消息的处理目标对象,用于处理消息
Handler target;
//用于标记该Message是否在被使用
int flags;
//存放时间,用于在MessageQueue中实现排序
long when;
//用于实现单链表,以链表实现Message池
Message next;
//链表头指针
private static Message sPool;
private static int sPoolSize = 0;//池的当前大小
private static final int MAX_POOL_SIZE = 50;//池的最大容量
...
}
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
Message使用了一个数据池sPool来对Message对象进行回收和再利用,所以建议用obtain方法来获取Message,避免多次分配对象,sPool的上限是MAX_POOL_SIZE = 50。 当开始obtain方法时首先开启了一个对象锁synchronized,如果当sPool不为空的时候,也就是有数据时,会取出表头的Message然后池内数据减1.
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
回收时会先检查一下当前的Message是否正在使用,然后调用recycleUnchecked方法进行回收,当使用完一个Message时,要将不使用的Message回收。recycleUnchecked方法回收之前先重置Message的状态,将状态都清空,将Message放回对象池时会首先判断对象池的容量是否已经满了,只有未满的时候才会回收进对象池,否则将会丢弃等待GC的回收。
除了 Message,Android 系统当中还有很多类也采用 obtain() 来获取实例,比如 MotionEvent、KeyEvent、AccessibilityEvent等。
- MotionEvent 和 KeyEvent 的 obtain() 的原理和 Message 几乎完全一致,都是使用单链表来维护实例的复用,不同的是 Event 池子的 Size 上限为 10
- AccessibilityEvent 则稍稍不同,obtain() 是从
SynchronizedPool里取得实例,其本质上是固定长度的数组,每次从最后一个元素获取和存入实例。
4、MessageQueue
是一个队列类型的数据结构,总体上就是先进先出的访问顺序,实现方式和Message对象池一样,也是由Message连接而成的单链表。Message有个long whent属性,就是存入MessageQueue的时间。而MessageQueue会按照when的大小将Message列表进行排序。
boolean enqueueMessage(Message msg, long when) {
//不接受没有目标处理对象的消息入队(即不收没有收件人的信件),也不接受正在使用中的消息入队
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
//若是该队列已经退出,则直接回收消息,只有在quit方法中才设置该属性为true
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
//标记正在使用,并设置时间
msg.markInUse();
msg.when = when;
//p指向队列(单链表)的头部
Message p = mMessages;//这里的队列也是由Message构成的单链表
boolean needWake;
//按照时间顺序,将时间值最小的放在队首,队首是最先出队的
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
//按照时间顺序(从小到大)将Message插在对应的位置。将新消息通过 when大小排序,存到消息队列中。
//消息队列实际上是一个单链表,when最小,即表示最先触发的消息,会放在链表头部
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
//这里在插入方法中可以看出,只要插入的Message不是异步的,那么
//needWake的值就会是mBlocked的值,而mBlocked的值会在出队方
//法next中,当线程阻塞的时候设为True。而这里当有非异步的Message入队时,
//就会调用nativeWake方法将线程唤醒来处理消息
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
enqueueMessage会按照时间从小到大的顺序将消息插入在相应的位置,也会判断是否需要唤醒线程。因为MessageQueue中的队列是由Message和它的属性next实现的单链表。单链表只能按照从头至尾的顺序访问,因为入队插在表尾,而出队是从表头取出的。
Message next() {
//ptr是native层对象指针,为0时表示MessageQueue已经结束
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
}
...
}
next方法中调用了一个死的for循环,以及一个nativePollOnce的native方法(陷入阻塞,等待被唤醒)。 在队列为空或消息暂未达到处理时间的时候,线程会阻塞,这里阻塞是通过native层的epoll方式进行的阻塞。
Message的执行时刻如何管理?
-
发送的 Message 都是按照执行时刻
when属性的先后管理在 MessageQueue 里- 延时 Message 的 when 等于
调用的当前时刻和delay之和 - 非延时 Message 的 when 等于
当前时刻(delay 为0) - 插队 Message 的 when 固定为
0,便于插入队列的head
- 延时 Message 的 when 等于
-
之后 MessageQueue 会根据读取的时刻和 when 进行比较
- 将 when 已抵达的出队,
- 尚未抵达的计算出当前时刻和目标 when 的插值,交由 Native 等待对应的时长,时间到了自动唤醒继续进行 Message 的读取
事实上,无论上述哪种 Message 都不能保证在其对应的 when 时刻执行,往往都会延迟一些!因为必须等当前执行的 Message 处理完了才有机会读取队列的下一个 Message。
比如发送了非延时 Message,when 即为发送的时刻,可它们不会立即执行。都要等主线程现有的任务(Message)走完才能有机会出队,而当这些任务执行完 when 的时刻已经过了。假使队列的前面还有其他 Message 的话,延迟会更加明显!
onCreate() 里向 Handler 发送大量 Message 会导致主线程卡顿吗?
不会,发送的大量 Message 并非立即执行,只是先放到队列当中而已。
onCreate() 以及之后同步调用的 onStart() 和 onResume() 处理,本质上也是 Message。等这个 Message 执行完之后,才会进行读取 Message 的下一次循环,这时候才能回调 onCreate 里发送的 Message。
需要说明的是,如果发送的是 FrontOfQueue 将 Message 插入队首也不会立即先执行,因为 onStart 和 onResume 是 onCreate 之后同步调用的,本质上是同一个 Message 的作业周期
异步Message或同步屏障
在Android的消息机制中,其实有三种消息: 普通消息、异步消息及消息屏障。
-
异步 Message:设置了
isAsync属性的 Message 实例- 可以用异步 Handler 发送
- 也可以调用 Message#
setAsynchronous()直接设置为异步 Message
-
同步屏障:在 MessageQueue 的某个位置放一个 target 属性为 null 的 Message,确保此后的非异步 Message 无法执行,只能执行异步 Message。可以通过MessageQueue中的postSyncBarrier方法发送一个消息屏障(该方法为私有,需要反射调用)。
-
原理:当 MessageQueue 轮循 Message 时候发现建立了同步屏障的时候,会去跳过其他 Message,读取下个 async 的 Message 并执行,屏障移除之前同步 Message 都会被阻塞
-
应用:比如屏幕刷新
Choreographer就使用到了同步屏障,确保屏幕刷新事件不会因为队列负荷影响屏幕及时刷新。 -
注意:同步屏障的添加或移除 API 并未对外公开,App 需要使用的话需要依赖反射机制
所以消息屏障和异步消息的作用很明显,在设置消息屏障后,异步消息具有优先处理的权利。
这时候我们回顾将消息添加到消息队列中时,可以发现,其实并不是每一次添加消息时,都会唤醒线程。 当该消息插入到队列头时,会唤醒该线程; 当该消息没有插入到队列头,但队列头是屏障,且该消息是队列中 靠前的一个异步消息,则会唤醒线程,执行该消息;
如何保证 MessageQueue 并发访问安全?
任何线程都可以通过 Handler 生产 Message 并放入 MessageQueue 中,可 Queue 所属的 Looper 在持续地读取并尝试消费 Message。如何保证两者不产生死锁?
Looper 在消费 Message 之前要先拿到 MessageQueue 的锁,只不过没有 Message 或 Message 尚未满足条件的进行等待前会事先释放锁,具体在于 nativePollOnce() 的调用在 synchronized 方法块的外侧。
Message 入队前也需先拿到 MessageQueue 的锁,而这时 Looper 线程正在等待且不持有锁,可以确保 Message 的成功入队。入队后执行唤醒后释放锁,Native 收到 event 写入后恢复 MessagQueue 的读取并可以拿到锁,成功出队。
这样一种在没有 Message 可以消费时执行等待同时不占着锁的机制,避免了生产和消费的死锁。
5、Looper
默认创建一个线程,线程里面是没有消息队列的,如果想用消息队列MessageQueue,就需要通过Looper进行绑定。下面是一个简单的例子:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
可以看见Thread通过Looper.prepare() 和 Looper.loop()两个静态方法运行。
从源码里看 Looper的构造函数是private,则说明Looper不能再外部实例化。就可以猜测到Looper和Thread是一一对应的。
private Looper(boolean quitAllowed) {
//当创建一个Looper实例时,会自动创建一个与之配对的MessageQueue(消息队列)
mQueue = new MessageQueue(quitAllowed);
//获取当前线程。
mThread = Thread.currentThread();
}
Looper有两个方法可以得到Looper对象,prepareMainLooper(主线程调用)和prepare方法。prepare会先判断是否已经创建过,创建过会抛出异常,所以一个线程只能对应一个looper。如果没创建过,就会创建一个looper放到ThreadLocal中,而ThreadLoacl是按照线程信息存储数据的,获取时也是根据当前线程获取对应数据。
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//判断sThreadLocal是否为null,否则抛出异常 即Looper.prepare()方法不能被调用两次
//也就是说,一个线程中只能对应一个Looper实例
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
```
//sThreadLocal是一个ThreadLocal对象,用于在一个线程中存储变量
//实例化Looper对象并存放在ThreadLocal 这说明Looper是存放在Thread线程里的
sThreadLocal.set(new Looper(quitAllowed));
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
Looper.prepare()调用完之后Looper就准备好了,接着就可以通过Looper.loop()开启消息循环,阻塞式地从MessageQueue中取消息。loop会创建一个消息队列MessageQueue,每个Looper会对应一个消息队列。调用 loop方法就会执行死循环一直从消息队列中取消息。
- Looper抽象了无限循环的过程,并且将MessageQueue从Handler中移到Looper中。
- ThreadLocal将每个线程通过ThreadLocalMap将Looper与Thread绑定,保证能够通过任意Thread获取到对应的Looper对象,进而获取到Thread所需的关键MessageQueue.
死循环未造成卡顿的原因是MessageQueue的next方法中,若是队列中没有Message时,则会阻塞在这里。因此不会造成系统的卡顿。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取当前线程的MessageQueue。
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
//获取MessageQuene消息队列的消息.
Message msg = queue.next(); // might block
//如果消息队列没有消息,则return,即阻塞在这里,等待获取Message。
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// msg.target 是一个Handler,
// 这个意思是让改Message关联的Handler通过dispatchMessage()处理Message。
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
Looper为什么要手动quit?
创建Looper并执行loop()的线程在任务结束的时候,需要手动调用quit。反之,线程将由于 loop() 的轮询一直处于可运行状态,CPU资源无法释放。更有可能因为Thread作为GC Root 持有超出生命周期的实例引发内存泄漏。
当quit调用后,Looper不再因为没有Message去等待,而是直接取到为null的 Message,这将触发轮循死循环的退出。
quit时Message们怎么处置?
Looper的很多处理实则都是MessageQueue在发挥作用,包括这里的Looper#quit()。它其实是调用 MessageQueue的同名函数quit(boolean),并指定safe参数为false。
public void quit() {
// 默认是不安全的退出
mQueue.quit(false);
}
MessageQueue#quit()则主要执行几项简单工作,包括:标记正在退出,并清空所有未执行的Mesage,最后唤醒线程。
- 退出的标记将导致后续的
sendMessage()或postRunnable()失效,直接返回false。 - 默认的策略是清空队列里所有Message,包括时间正好抵达的Message都无法处理,不太友好。
- 最后唤醒线程,进入读取队列的下一次循环,因为队列已无Message,将直接返回null。
- loop()拿到的Message为null,死循环退出,线程结束。
quitSafely做了哪些优化?
quitSafely调用的那刻,满足执行时间条件的Message继续保留在队列中,在都执行完毕才退出轮询。移除未来Message之后唤醒线程的next()循环,其将取出留在队列里的Message进行处理。等残存 Message 都执行完了,下一次轮询的next()将取不到Message,最终因为quitting flag返回null,进而触发 loop()死循环的退出。
主线程Looper需要quit吗?
主线程ActivityThread创建Looper时指定了不允许quit的标志,即不可以手动调用quit。 如果强行在主线程里调用了 quit(),会发生如下异常:
java.lang.IllegalStateException: Main thread not allowed to quit.
// MessageQueue.java
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
...
}
综上:主线程也不需要退出,在内存不足的时候App由AMS直接回收进程。主线程极为重要,承载着 ContentProvider、Activity、Service 等组件生命周期的管理,即便某个组件结束了,它仍有继续存在去调度其他组件的必要!
Looper 存在哪?如何可以保证线程独有?
- Looper 实例被管理在静态属性
sThreadLocal中 ThreadLocal内部通过ThreadLocalMap持有 Looper,key为 ThreadLocal 实例本身,value即为 Looper 实例- 每个 Thread 都有一个自己的 ThreadLocalMap,这样可以保证每个线程对应一个独立的 Looper 实例,进而保证
myLooper()可以获得线程独有的 Looper
主线程 Main Looper 和一般 Looper 的异同?
-
区别:
- Main Looper在创建的时候设置了不可
quit的标志,不可quit。主线程需要不断读取系统消息和用书输入,是进程的入口,只可被系统直接终止。 - Main Looper 实例还被静态缓存。为了便于每个线程获得主线程 Looper 实例,见 Looper#getMainLooper(),Main Looper 实例还作为
sMainLooper属性缓存到了 Looper 类中。
- Main Looper在创建的时候设置了不可
-
相同点:
-
都是通过 Looper#prepare() 间接调用 Looper 构造函数创建的实例
-
都被静态实例 ThreadLocal 管理,方便每个线程获取自己的 Looper 实例
-
主线程为什么不用初始化 Looper?
App 的入口并非 MainActivity,也不是 Application,而是 ActivityThread。 其为了 Application、ContentProvider、Activity 等组件的运行,必须事先启动不停接受输入的 Looper 机制,所以在ActivityThread的 main() 方法中调用 prepareMainLooper() 创建 Looper 并调用 loop() 轮循。
public static void main(String[] args) {
...
Looper.prepareMainLooper();//创建属于此线程的Looper
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
Looper.loop();
}
public static void prepareMainLooper() {
prepare(false);
...
}
private static void prepare(boolean quitAllowed) {
...
sThreadLocal.set(new Looper(quitAllowed));
}
Handler 或者说 Looper 如何切换线程?
- Handler 创建的时候指定了其所属线程的 Looper,进而持有了 Looper 独有的 MessageQueue
- Looper#loop() 会持续读取 MessageQueue 中合适的 Message,没有 Message 的时候进入等待
- 当向 Handler 发送 Message 或 Runnable 后,会向持有的 MessageQueue 中插入 Message
- Message 抵达并满足条件后会唤醒 MessageQueue 所属的线程,并将 Message 返回给 Looper
- Looper 接着回调 Message 所指向的 Handler Callback 或 Runnable,达到线程切换的目的
简言之,向 Handler 发送 Message 其实是向 Handler 所属线程的独有 MessageQueue 插入 Message。而线程独有的 Looper 又会持续读取该 MessageQueue。所以向其他线程的 Handler 发送完 Message,该线程的 Looper 将自动响应。
Looper 的 loop() 死循环为什么不卡死?
为了让主线程持续处理用户的输入,loop() 是死循环,持续调用 MessageQueue#next() 读取合适的 Message。但当没有 Message 的时候,会调用 pollOnce() 并通过 Linux 的 epoll 机制进入等待并释放资源。同时 eventFd 会监听 Message 抵达的写入事件并进行唤醒。
这样可以空闲时释放资源、不卡死线程,同时能持续接收输入的目的。
Looper 等待的时候线程到底是什么状态?
调用 Linux 的 epoll 机制进入等待,事实上 Java 侧打印该线程的状态,你会发现线程处于 Runnable 状态,只不过 CPU 资源被暂时释放。
Looper 的等待是如何能够准确唤醒的?
读取合适 Message 的 MessageQueue#next() 会因为 Message 尚无或执行条件尚未满足进行两种等的等待:
-
无限等待
无 Message(队列中没有 Message 或建立了同步屏障但尚无异步 Message)的时候,调用 Natvie 侧的pollOnce()会传入参数 -1。Linux 执行
epoll_wait()将进入无限等待,其等待合适的 Message 插入后调用 Native 侧的wake()向唤醒 fd 写入事件触发唤醒 MessageQueue 读取的下一次循环 -
有限等待
有限等待的场合将下一个 Message 剩余时长作为参数交给 epoll_wait(),epoll 将等待一段时间之后自动返回,接着回到 MessageQueue 读取的下一次循环
Native 侧的 NativeMessageQueue 和 Looper 的作用是?
- NativeMessageQueue 负责连接 Java 侧的 MessageQueue,进行后续的
wait和wake,后续将创建 wake 的FD,并通过 epoll 机制等待或唤醒。但并不参与管理 Java 的 Message - Native 侧也需要 Looper 机制,等待和唤醒的需求是同样的,所以将这部分实现都封装到了 JNI 的NativeMessageQueue 和 Native 的 Looper 中,供 Java 和 Native 一起使用
Native 侧如何使用 Looper?
- Looper Native 部分承担了 Java 侧 Looper 的等待和唤醒,除此之外其还提供了 Message、
MessageHandler或WeakMessageHandler、LooperCallback或SimpleLooperCallback等 API - 这些部分可供 Looper 被 Native 侧直接调用,比如
InputFlinger广泛使用了 Looper - 主要方法是调用 Looper 构造函数或 prepare 创建 Looper,然后通过 poll 开始轮询,接着
sendMessage或addEventFd,等待 Looper 的唤醒。使用过程和 Java 的调用思路类似
6、Handler
Handler 是暴露给外部调用者使用,Handler有多个构造函数。
- public Handler()
- public Handler(boolean async)
- public Handler(Callback callback)
- public Handler(Callback callback,boolean async)
- public Handler(Looper looper)
- public Handler(Looper looper, Callback callback)
- public Handler(Looper looper, Callback callback, boolean async) 前4个构造函数是没有传Looper的,他将获取该线程的Looper和MessageQueue消息队列
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
后3个构造函数有传Looper,这两个构造函数会将该Looper保存到名为mLooper的成员字段中
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public interface Callback {
public boolean handleMessage(Message msg);
}
Handler.Callback是用来处理Message的一种手段,在构造函数中传递Callback,则可以处理Message,如果返回的是true,则不再往下执行,起到拦截的效果。如果构造函数没有传递Callback,则应在Handler中重写handleMessage方法。
sendMessage 发送消息
在线程中可以通过sendMessage方式往消息队列中添加Message。
- sendMessage(Message msg):
- sendMessageDelayed(Message msg, long delayMillis):
- sendMessageAtTime(Message msg, long uptimeMillis) :
- sendEmptyMessage(int what)
- sendEmptyMessageDelayed(int what, long delayMillis)
- sendEmptyMessageAtTime(int what, long uptimeMillis) :
通过看Handler源码可以知道,最终都是调用sendMessageAtTime方法。而在sendMessageAtTime中通过enqueueMessage方法将Message放入消息队列中。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//将Message的target绑定为当前的Handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- msg.target = this : 将Message的target绑定为当前的Handler .
- queue.enqueueMessage(msg, uptimeMillis): queue 是当前Handler的消息队列MessageQueue.通过queue.enqueueMessage将Message放入消息队列。
post 方式发送消息
- post(Runnable r)
- postAtTime(Runnable r, long uptimeMillis)
- postAtTime(Runnable r, Object token, long uptimeMillis)
- postDelayed(Runnable r, long delayMillis) 其中post 和 postDelayed 最后还是调用sendMessageDelayed方法。postAtTime最后调用sendMessageAtTime方法,最终的调用和sendMessage一样。
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
通过上面的代码我们可以看到在getPostMessage方法中,我们创建了一个Message对象,并将传入的Runnable对象赋值给Message的callback成员字段,然后返回该Message,然后在post方法中该携带有Runnable信息的Message传入到sendMessageDelayed方法中。
dispatchMessage
构造方法中的一些属性,都是从Looper中的ThreadLocal中获取的。enqueueMessage时将msg.target赋值为this,将来在looper.loop中调用target.handleMessage。若是没有指定looper,则使用实例化Handler时所在线程的Looper,否则使用指定的Looper。如果构造方法传入callback,则执行callback的handleMessage处理,否则使用Handler本身的handlerMessage处理消息。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
7、IdelHandler
使用当前线程的MessageQueue.addIdleHandler方法可以在消息队列中添加一个IdelHandler。
MessageQueue messageQueue = Looper.myQueue();
messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return false;
}
});
当MessageQueue 阻塞时,即当前线程空闲时,会回调IdleHandler中的方法;添加IdelHandler时,消息队列不为空,当消息处理完或者剩下消息还没到触发时间,会回调方法;当添加IdelHandler时,消息队列为空,则当时不会触发回调. 当IdelHandler接口返回false时,表示该IdelHandler只执行一次,
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
......
synchronized (this) {
......
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
}
}
使用场景
1、延迟执行 2、批量任务,任务密集,且只关注最终结果。
8、原理小结
1、在使用Handler的时候,在Handler所创建的线程需要维护一个唯一的Looper对象,每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证。Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,但是只能有一个Looper和一个MessageQueue。
2、Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个 Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。
3、Looper对象通过loop()方法开启了一个死循环,不断地从Looper内的MessageQueue中取出Message, 然后通过Handler将消息分发传回Handler所在的线程。
9、HandlerThread
HandlerThread其实就是Thread类,它跟普通的Thread相比就是多了一个Looper,但就是多了这么一个Looper让我们省却自定义Looper和担心由于线程并发而造成Looper在未创建成功就被调用的的麻烦。
HandlerThread的常规用法
- 启动线程,构造参数:String代表线程名,priority代表优先级。优先级范围为-20到19,默认为0,优先级越高,获得的CPU资源更多,反之则越少。-20代表优先级最高,反之19最低。
mThread = new HandlerThread("handler_thread");
mThread.start();
- 创建处理任务的mWorkHandler和更新UI的mUIHandler。
mWorkHandler = new Handler(mThread.getLooper());
mUIHandler = new Handler();
- mWorkHandler与HandlerThread的Looper关联,并在handleMessage(Message msg)中处理任务,处理完之后通知mUIHandler对UI进行刷新。
- 在合适的时机退出HandlerThread,比如activity中的onDestroy(),方法有quit()和quitSafely()
具体处理方式要看具体需求,不过总体思路跟上面三个步骤差不多。如果想处理多个任务。就发送多个消息,在mWorkHandler进行处理。
HandlerThread的特点
- HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。
- 开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。
相比多次使用new Thread(){…}.start()这样的方式节省系统资源。
但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。 - HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
- 通过设置优先级就可以同步工作顺序的执行,而又不影响UI的初始化;
HandlerThread比较适用于单线程+异步队列的场景,比如IO读写操作,耗时不多而且也不会产生较大的阻塞。对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。
10、关于内存泄露
Handler泄露本质是由于长生命周期的对象Thead间接持有了短生命周期的对象造成。
造成内存泄露的使用场景:
- Handler采用匿名内部类或内部类扩展,默认持有外部类
Activity的引用 - Activity退出的时候Handler仍可达,有两种情况:
- 退出的时候仍有Thread在处理中,其引用着Handler
- 退出的时候虽然Thread结束了,但Message尚在队列中排队处理或正在处理中,间接持有Handler
原因:异步任务仍然活跃或通过发送的Message尚未处理完毕,将使得内部类实例的生命周期被错误地延长。造成本该回收的Activity实例被别的 Thread 或 Main Looper 占据而无法及时回收。
正确做法:
- 无论是 Handler、Handler$Callback 还是 Runnable,尽量采用静态内部类 + 弱引用的写法,确保尽管发生不当引用的时候也可以因为弱引用能清除持有关系
- 另外在 Activity 销毁的时候及时地终止 Thread、停止子线程的 Looper 或清空 Message,确保彻底切断 Activity 经由 Message 抵达 GC Root 的引用源头。
11、性能优化
复杂情况下,可能会频繁调用sendMessage 往消息队列中,添加消息,导致消息积压,造成卡顿,
1,重复消息过滤
频繁发送同类型消息时,有可能队列中之前的消息还没有处理,又发了一条相同类型的消息,更新之前的数据,这时候,可以采用移除前一个消息的方法,优化消息队列。
private void sendTypeMsg(){
Message msg = Message.obtain();
msg.what = MSG_TYPE;
handler.removeMessages(MSG_TYPE);
handler.sendMessage(msg);
}
2,互斥消息取消
在发送消息时,优先将消息队列中还未处理的信息已经过时的消息 移除,优化队列
3,队列优化-复用消息
创建消息时,优先采用之前回收的消息,避免重复创建对象,引起GC