Handler
Android整个ui体系都是围绕handler的消息机制,handler可以在子线程发送消息,在主线程接收处理消息,从而实现线程的跨越,所有有必要对其进行更加深入的掌握
从一般的使用上看,handler的使用分为发送消息和接收消息
//实例化一个handler对象
val mHandler = Handler(Looper.getMainLooper()){
//do some thing in main thread
false
}
//通过handler对象发送消息
mHandler.sendEmptyMessage(-1)
消息的发送
使用还是很熟悉的,发送端发送消息,除了发送空的消息,还可以传递一个带参数的Messge对象,还有延时发送消息的api方法
具体的看一下其中的调用流程
这里有一些需要注意的点
Message的实例化是通过Message.obtain()方法,而不是直接new Message()
Message对象会持有发送者Handler的引用,并赋值在msg.target 属性上
在msg入队之前,会判断mAsynchronous属性的值,并将msg对应的值进行赋值
通过Handler的send操作,就会产生一个附带有额外信息的Message对象和相应的延迟时间属性,那么看看mQueue这个队列是从哪来的
Handler中的Looper
//Handler
public class Handler {
//mQueue是Handler的一个成员属性
final MessageQueue mQueue;
final Looper mLooper;
public Handler(@Nullable 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;
mCallback = callback;
mAsynchronous = async;
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
}
Handler中的mQueue和mLooper属性都是在Handler()构造函数中进行赋值引用的,可以看出handler中持有的MessageQueue是从对应的Looper中拿到的,那看一下Looper这个类
public final class Looper {
//ThreadLocal的常量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private Printer mLogging;
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//Looper的创建
public static void prepare() {
prepare(true);
}
//从ThreadLocal中去获取对应的Looper,这个方法只能调用一次,如果重复调用,会抛出异常
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//会直接通过new Looper()的方式创建一个Looper,并调用ThreadLocal$set进行保存
sThreadLocal.set(new Looper(quitAllowed));
}
}
//Looper初始化后调用的保存方式
public class ThreadLocal<T> {
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;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//从下面可以Thread类可以看出,每一个线程会有一个ThreadLocalMap的成员属性
ThreadLocalMap map = getMap(t);
if (map != null)
//Map其实就是一个hashmap,里面会有一个Entry数组,Entry保存了一个键值对,而key的类型就是ThreadLocal
map.set(this, value);
else
createMap(t, value);
}
//获取对应线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
//...省略
tab[i] = new Entry(key, value);
}
}
小结一下:Looper的初始化和线程唯一
-
Handler构造函数初始化,传入了Looper参数就直接赋值,没有传入就通过Looper.myLooper()获取
构造函数让Handler的mLooper对象有值,并且将其中的MessageQueue进行传递
-
Looper.myLooper()的函数->调用其内部的 sThreadLocal.get()
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal是一个常量,所以得看其内部具体的get()逻辑
-
ThreadLocal的set()/get()方法都是先获取当前的线程#Thread.currentThread()#,然后获取线程内部的ThreadLocalMap对象,这个Map对象的key就是ThreadLocal的一个弱引用
Looper初始化的时候,调用#prepare()方法,会通过new Looper()的方法直接实例化一个Looper对象,通过ThreadLocal的set方法,会将这个Looper对象设置到对应线程的ThreadLocalMap属性中,这个属性其实是一个HashMap,对应的key就是Looper中的常量ThreadLocal,所以线程通过这个key会找到唯一对应的Looper值
不同的线程对应的ThreadLocalMap是不同的对象,虽然都是looper中的常量ThreadLocal作为key,但是容器不一样,所以Looper是线程唯一的
线程内的Handler可以直接sendmessage()吗
//在线程里面调用一个不传Looper参数的Handler构造方法
//调用Looper.myLooper()->sThreadLocal.get() 由于该线程没有初始化设置Looper对象,所以这样写会崩溃
new Thread(new Runnable() {
@Override
public void run() {
//线程里面
// Looper.prepare();
// Looper.loop();
Handler mHandler = new Handler(msg -> {
return false;
}
);
mHandler.sendEmptyMessage(-1);
}
}).start();
//错误信息,线程没有调用Looper.prepare(),所以找不到对应的looper对象
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
通过上面的流程,对Handlder和Looper的关系有一个理解了
Handler中有一个Looper的成员属性,默认情况下,这个Looper对象是handler所属当前线程的唯一值
主线程中的Looper
public final class ActivityThread extends ClientTransactionHandler {
public static void main(String[] args) {
//...省略各种不相关的代码
Looper.prepareMainLooper();
Looper.loop();
}
}
->Looper.java
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
app进程的main()函数在ActivityThread类中,从里面可以看出,最终也是调用了Looper$prepare()方法,对我们熟悉的主线程进行Looper的初始化
Looper中的MessageQueue
public final class Looper {
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
}
从上文可以看到,Looper.prepare()方法中,会直接new一个Looper对象,而Looper的构造函数中,也是直接new了一个MessageQueue对象
queue.enqueueMessage
最前面的handler发送逻辑,最后msg对象和延迟时间都是通过queue.enqueueMessage()进行操作的,现在就分析一下怎么入队的
public final class MessageQueue {
private final boolean mQuitAllowed;
Message mMessages;
boolean enqueueMessage(Message msg, long when) {
//target就是handler的引用
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//子线程可以使用主线程的handler进行发送消息,入队的操作需要同步锁
synchronized (this) {
//退出循环了,后续入队的直接抛异常
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;
Message p = mMessages;
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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
}
//结合Message的数据结构
public final class Message implements Parcelable {
//链表的下一个节点的引用
Message next;
//用来回收复用加锁
public static final Object sPoolSync = new Object();
//对象池也是使用的链表
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;
}
}
//回收池里面没有可复用的对象,直接new
return new Message();
}
//回收消息对象
void recycleUnchecked() {
//清洗对象的属性值
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
//因为有链表的首尾关系,需要加锁
synchronized (sPoolSync) {
//最大回收池大小是50个
if (sPoolSize < MAX_POOL_SIZE) {
//回收的对象是插在头部的,将回收对象的next指针指向当前的头节点
next = sPool;
//头节点又引用当前回收的对象
sPool = this;
sPoolSize++;
}
}
}
}
Message入队过程
Message使用了链表的数据结构,通过next属性指向下一个Message对象
Message中有个sPool的回收池,回收池就是Message的链表队列
通过obtain()方法会从回收池中去取头节点,存在会将回收池后移,如果不存在直接new出一个对象
通过recycleUnchecked()回收Message,也是清空属性后插入到头节点
-
MessageQueue就是一个Message的链表,入队的时候,通过判断Message的延迟时间,进行排序插入
通过上面分析,handler发送消息入队的操作,最终会得到一个按延迟时间排序的有序链表,发送端基本就分析完了,接下来看一看消息的消费
消息的消费
上面注意到的点,在Looper.prepare()初始化了Looper对象后,然后不断的往Looper中的messageQueue发送消息,代码层面对Looper的操作就只有 Looper.loop()方法,所以消息的消费就在这里面
###Looper.java
public static void loop() {
//获取当前线程的looper
final Looper me = myLooper();
//取出对应的MessageQueue
final MessageQueue queue = me.mQueue;
//消息消费的关键代码,通过一个死循环进行轮询取消息
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//Looper中如果设置Printer对象,消息的消费过程会输出日志
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//...省略
try {
//取出发送消息的handler,回调到我们的界面的地方
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
//...
} finally {
//...
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
//消息回收复用
msg.recycleUnchecked();
}
}
Message的消费还是比较简单,通过Looper的死循环,不断的从消息队列中去取消息,然后将对应的消息分发出去,就能回调到我们熟悉的dispatchMessage()方法中了,最后将消息进行回收复用
值得注意的点
- Looper对象中如果设置了Printer属性,在消息分发的过程中,开始处理和处理结束都会打印日志输出
public interface Printer {
/**
* Write a line of text to the output. There is no need to terminate
* the given string with a newline.
*/
void println(String x);
}
Printer是一个接口,这个点可以用来做简单的页面FPS监控,页面的刷新最终也是通过handler机制发送的一个发送一个message进行刷新绘制
总结
终于从前到后从源码中找到关键代码,梳理了一遍常用的消息机制handler的发送和消费过程,其中涉及到相关的Looper和Message对象,记录了Looper的线程唯一性的原因
一些有所了解还没整理总结的点
- 对于looper死循环的退出(exit())和主线程死循环没有ANR相关的epoll机制暂时没有分析,epoll的底层原理理解起来有点麻烦,通俗一点就是messagequeue队列里面没有message需要处理的时候,会通过epoll机制进行休眠,然后在需要处理的时候wake唤醒进行消息处理
- message的target==null的情况下,消息屏障处理异步消息的流程没有分析
handler这个贯穿整个app的生命周期,也经常在群里看到讨论Message的复用机制,MessageQueue的设计,Looper的线程唯一性,MessageQueue的池子大小等问题,还是得梳理源码,一下就清晰了,如果有理解错误的地方,欢迎指导