Handler是老生常谈的一个东西,无论是面试,还是项目操作,各大系统底层都绕不开它。
Handler的概述
Handler是消息通信的一种机制。Handler由Message(消息)、MessageQueue(消息队列)、Looper(轮循器)进行支撑。Handler的主要作用是将一个任务切换到某个指定线程中去执行。常用在子线程中进行耗时操作后要进行UI的更新。
Handler的工作原理
Handler创建完成后,通过post或者sendMessage方法将一个Runnable投递到Looper中去处理,post最终是通过send方法完成的。
当send方法被调用时,通过MessageQueue的enqueueMessage方法将消息放入消息队列中。然后Looper发现有新消息来时,就会处理消息,最终消息中的Runnable或者Handler的handleMessage就会被调用。
关于ThreadLoacl
ThreadLocal是一个线程内部的数据存储类。通过它可以在指定的线程中存储数据。一般来说,当某些数据以线程为作用域且不同线程具有不同的数据副本的时候,就可以通过ThreadLoacl。其中Handler得Looper的作用域就是线程并且不同的线程具有不同的Looper。
另一个作用做对象传递。例如函数调用栈比较深,代码入口多向性,这种情况需要监听器贯穿整个线程的执行过程。就可以通过ThreadLoacl的get方法获取。
以下是set和get源码
set方法
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //从当前线程中获取map
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
ThreadLocalMap是线程中的局部变量,说明每一个线程中都有一个自己的局部变量ThreadLocal,并将当前线程作为key存储Looper。
get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; // 通过线程获取value
return result;
}
}
return setInitialValue();
}
通过获取当前线程的key,来确保每一个线程的唯一性。
关于MessageQueue
消息队列主要包含两个操作,插入(enqueueMessage)和读取(next)。enqueueMessage负责往消息队列中插入一条数据,next负责从消息队列中取出一条数据并将其从消息队列中移除。其中enqueueMessage的内部实现是通过一个单链表的数据结构来维护消息,单链表的优势在插入和删除上有优势。
而next就是一个无限循环,如果消息队列没有消息,那么next方法就会一直阻塞等待。有兴趣的可以去点开Handler中的send方法翻阅一下源码。
关于Looper
主要负责消息循环。
loop() 本身就是一个死循环
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
///...略...
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
跳出循环的方法就是MeessageQueue的next方法返回了null。
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
//...略...
}
关于Handler
上边也讲过,send和post方法,最终调用都是send方法。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
在最后会调用sendMessageAtTime并拆入一条消息,交由looper处理,如此循环就完成了Handler消息处理。
无构造参数的Handler创建已经被标记为了过时,在创建Handler时必须指定一个Looper。
关于主线程的消息循环
主线程就是ActivityThread,主线程的入口方法为main(),在main中通过Looper.prepare()来创建主线程的Looper以及MessageQueue并通过Looper.loop()来开启主线程中的的消息循环。而所需的Handler,则在ActivityThread.H中,H内部定义了一组消息类型,包括四大组件的启动和停止。
通信过程就是通过Application和AMS,然后通过AMS的回调ApplicationThread的Binder方法。然后ActivityThread向H发送消息,H收到消息后会将ApplicationThread的逻辑切换到ActivityThread(主线程)中执行。
扩展
1.为什么不允许在子线程中访问UI呢?
因为Android的UI控件不是线程安全的,如果在多线程中并发访问会出现不可控状态。如果加上锁机制,会让UI的逻辑变得复杂,再就是锁机制会降低UI访问的效率,锁机制会阻塞某些线程的执行。所以最简单高效的方法就是采用单线程模型来处理UI操作。
2.Handler如何保证只有一个Looper?
在每个线程中的ThreadLocalMap中,并且将当前线程作为Key。当我们通过Handler创建子线程的时候,先调用prepare()方法,来确保ThreadLocal中只有一个Loope
3.ThreadLocal会内存泄漏吗?
不会,因为ThreadLoalMap是通过弱引用的形式继承实现的。
4.如何区分异步消息和同步消息?
// TODO
5.子线程中更新UI的方式
Thread {
Looper.prepare()
//...略...
Looper.loop()
}