一、背景
Handler相关的知识在面试的时候经常出现,而且问法千奇百怪。Handler相关的知识有经常的给忘记,为此歇息写下这篇文章。主要解决以下问题:
- Handler知识点梳理回顾
- Handler如何进行线程切换?
二、Handler简单回顾
Handler主要作用就是将一个任务切换到指定的线程中执行。
Handler机制-MessageQueue:
MessageQueue即"消息队列",它内部存储了一组数据,以队列的形式对外提供插入和
删除的操作。
虽然称之为"队列",实际上它的数据机构却采用了单链表的结构来存储消息列表。
MessageQueue主要包含两个操作:
-
插入(enqueueMessage):
-
读取(next):
从消息队列中取出一条数据并将该数据从消息队列中删除。
MessageQueue:同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列
来保存消息和安排每个消息的处理顺序。
Handler机制-Looper
Looper可以理解为消息循环,因为MessageQueue只是一个存储消息队列,不能去处理消息,所以需要Looper无限循环的去查看是否有新的消息,如果有的话就处理消息,否则就一直等待(阻塞)。**每一个异步线程,都维护着唯一的一个Looper,每一个Looper会初始化(维护)一个MessageQueue,**之后进入一个无限循环一直在读取MessageQueue中存储的消息,如果没有消息那就一直阻塞等待。
- Prepare();
- 检查是否实例化了ThreadLocal,确保每个线程的Looper只有一个。
- 如果已经实例化ThreadLocal,则将所在线程中Looper对象存储进去。
- 将线程-looper-MessageQueue绑定。
private static void prepare(boolean quitAllowed) {
...
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
线程通过ThreadLocal保存Looper对象,而Looper在初始化的时候创建了MessageQueue
- Loop()
- 获取Looper对应的MessageQueue,并从中获取Message,交给消息的target(Handler)属性的dispatchMessage去处理。
public static void loop() {
// 获取当前线程对应的Looper
final Looper me = myLooper();
// 获取Looper对应的MessageQueue
final MessageQueue queue = me.mQueue;
for (;;) {
// 通过Message获得Message
Message msg = queue.next(); // might block
try {
// 通过Message的target(handler)分发消息
msg.target.dispatchMessage(msg);
} finally {
}
// 回收消息
msg.recycleUnchecked();
}
}
Handler机制-Handler
通过Handler将当前线程对应的Looper、MessageQueue绑定
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
}
小结:
Handler是用于同一个进程的线程间通信。Looper让主线程无限循环地从自己的MessageQueue拿出消息处理,所以我们就可以知道处理消息肯定是在主线程中处理的,那么是怎样在其他的线程往主线程的队列里放入消息呢?
道理其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到MessageQueue 的实例,就可以往主线程的MessageQueue放入消息,主线程在轮询的时候就可以在主线程处理这个消息。
那么是怎么拿到主线程 MessageQueue的实例呢?
当然是可以拿到的(在主线程下mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。
Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。
三、总结
Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。
同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的
四、参考
MessageQueue的插入(enqueueMessage)详细过程?
MessageQueue的读取(next)详细过程?