- 一、Handler概述
- 二、Handler发送消息的方法
- 三、MessageQueue的enqueueMessage()
- 四、Message的when字段
- 五、子线程中使用Handler
- 六、Looper.loop()
- 七、获取下一个消息MessageQueue的next()
- 八、Handler的dispatchMesssage()
Handler在native层还有很多知识点,这篇主要围绕java层。
一、Handler概述
1. 什么是Handler?
Handler可以将一个任务切换到指定线程中执行,常用来实现在子线程工作完后切换到UI线程更新UI。
在ViewRootImpl中的checkThread()会检查当前线程,在更新UI时会调用到这个方法,所以在非UI线程中执行UI操作就会抛出此异常。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
UI控件不是线程安全的,多线程并发访问就会出现问题,所以只允许在主线程更新UI。之所以UI控件没有考虑上锁保证线程安全,是因为上锁会让代码逻辑变得复杂且会降低UI访问的效率。
2. Handler底层
Handler的运行需要底层的MessageQueue、Message和Looper来支撑。
a. MessageQueue
- MessageQueue是消息队列。
- 采用单链表的形式存储消息列表。
- 向外提供对Message的插入和删除工作。
b. Message
- 消息实体。
c. Looper
- Handler创建的时候采用当前线程的Looper来构造消息循环系统。
- Looper以无限循环的形式查看是否有新消息。
- 有新消息时,就将Message对象从MessageQueue中取出,并将其交给Handler的dispatchMessage()。
- Looper属于单个线程实例,通过ThreadLocal获得。
ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以很轻松地获取每个线程的Looper。
- 线程中默认是没有Looper的,如果需要,就要为线程创建Looper。
- 主线程在ActivtyThread创建时会初始化Looper,所以主线程默认可以使用Handler。
3. Handler工作步骤
- Handler创建,采用当前线程的Looper构建内部消息系统。
- Handler的post()、send()等方法调用MessageQueue的enqueueMessage()将一个Message加入到消息队列。
-
Looper发现新消息的到来,处理这个消息。
二、Handler发送消息的方法
发送消息的方法比较多,主要可以分为两类,一类sendXXXX(),一类postXxxx(),两类的主要区别就是send系的主要接受一个Message对象,而post系的主要接收一个Runnable对象,但最终也是封装成了Message对象。
boolean sendMessage(Message msg)
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
boolean sendMessageAtTime(Message msg, long uptimeMillis)
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);
}
最后调用到MessageQueue的enqueueMessage()。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
上面三个send系的方法最终都是调用了MessageQueue的enqueueMessage(),出入了Message对象和执行时间。
boolean sendEmptyMessageDelayed(int what, long delayMillis)
sendEmptyMessage()系列的方法和上述方法类似,只是不需要传入一个Message,Message在方法中默认构造了。
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
再来看post系的方法。
boolean post(Runnable r)
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
虽然传入的是Runnable对象,但是在方法调用过程中同样构造了Message,调用了send的方法,所以最终也是通过Message对象的形式交给MessageQueue的enqueueMessage()的。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
boolean postDelayed(Runnable r, long delayMillis)
postDelay()也同样是构造了Message对象,调用了sendMessageDelayed(),最终传递给MessageQueue的。
public final boolean postDelayed(Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
三、MessageQueue的enqueueMessage()
在Handler的enqueueMessage()中,赋值了Message对象的target属性为执行的Handler对象,之后就调用了MessageQueue的enqueueMessage()。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue的enqueueMessage()还是比较长的。
- 第一步是一些异常处理,先判断msg的target是否为空、判断msg是否正在使用,这个是否正在使用是在下面同步代码块中进行赋值的。
- 进入同步代码块了,判断如果该msg已经调用了quit()边不再继续,直接回收返回。
- 进行了一些赋值操作后就开始加队列的工作了。
- 首先如果队列中还没有Message或该Message执行时间为0或是小于队列中的第一个Message的时间,就将该Message作为队列的头,并更新头Message记录。
- 否则遍历队列,将Message插入到合适的位置,可以看出整个队列是按Message的when(一个相对时间,表示执行时间)字段从小到大排列的,如果两个Mssage的when字段相同,则先入队列的排在前面。
- 最后,如果需要唤醒MesssageQueue的next()就唤醒(MessageQueue的next()在没有消息的时候会阻塞,而一旦添加了新消息,enqueueMessage()被调用,自然就唤醒了next())。
boolean enqueueMessage(Message msg, long when) {
// 1.
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) {
// 2.
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;
}
//3.
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 4.
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 {
// 5.
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;
}
// 6.
if (needWake) {
// 此处唤醒next()。
nativeWake(mPtr);
}
}
return true;
}
四、Message的when字段
上面我们通过分析MessageQueue的enqueueMessage(),知道了Message队列是按照Message对象的when字段去排列的,那么when字段是什么呢?
在Handler的sendMessageDelayed中可以看到Message对象的when字段是如何初始化的。when的值是SystemClock.uptimeMillis() + delayMillis
。
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
SystemClock#uptimeMillis()
表示当前时间的一个相对时间,代表自系统启动开始从到调用该方法时度过的毫秒数。加上传参设置的delayMillis,整个when表示的时间代表该Message期望被分发的相对时间。
提供时间获取的方法有很多,为什么要用该系统启动到调用方法的相对时间,而不用System.currentTimeMillis()
呢,是因为它代表的是从1970-01-01 00:00:00到当前时间的毫秒数,这个值是一个强关联系统时间,我们可以通过修改系统时间达到修改该值的目的,所以该值是不可靠的值,会有可能导致延时消息失效。when字段只是用时间差来表示先后关系,所以只需要一个相对时间就可以达成目的。
五、子线程中使用Handler
在UI线程中,Looper早已在app启动过程中就为我们初始化好了,所以可以直接获取Handler对象并使用,但是在子线程中,必须手动去初始化Looper,之后将Handler绑定该子线程的Looper才能够使用。
Looper.prepare(); // 为子线程创建一个Looper
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "子线程的Handler");
}
};
mHandler.sendEmptyMessage(1);
Looper.loop();// 开始循环
可以向上面一样写,这里面有一些注意点。
- 在调用Handler之前必须调用Looper.prepare()方法,在当前线程中创建一个looper。我们也可以直接使用HandlerThread,其中是带有Looper的。
- Looper.loop()是无限循环的,所以在Looper.loop()后边的程序代码块是无法执行到的。loop()方法的主要作用是一直不断的通过queue.next()方法来读取来自messagequeue中的msg,这个方法是block的状态,如果queue中没有消息的话会一直阻塞在这里。
除了上面的写法,也可以在Handler构造函数传入Looper对象来绑定。
Handler handler = new Handler(looper);
上面两种绑定Looper的方法中,第二种很直观通过传参绑定了,那么第一种方法是如何绑定的呢?
public Handler() {
this(null, false);
}
看到这里调用了Looper的myLooper(),猜想就是利用了ThreadLocal获取到当前线程的Looper对象了。
public Handler(Callback callback, boolean async) {
// ......
mLooper = Looper.myLooper();
// ......
}
果然是调用了TreadLocal对象的get,该ThreadLocal对象泛型是Looper的。
static final ThreadLocal sThreadLocal = new ThreadLocal();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
在Looper的prepare()中对该ThreadLocal放入了Looper对象,所以在使用Handler之前要先调用Looper的prepare()。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
六、Looper.loop()
Looper.loop()
- 获取Looper对象并判空,再获取MessageQueue对象。
- 进入主循环,调用MessageQueue的next()去获取下一个消息,如果没有消息就一直阻塞。
- 如果返回消息为null,直接return,此时说明整个系统要处于退出状态了。
- 如果获取到了,就调用msg的Handler的dispatchMessage()执行消息。
- 之后将msg放入回收池中等待复用。
- 继续主循环,循环上述步骤,一直到退出。
public static void loop() {
// 获取looper对象
final Looper me = myLooper();
// 判空异常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 获取looper的MessageQueue
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// 开始死循环,不停获取消息队列中的消息并执行
for (;;) {
// 调用MessageQueue的next()获取新消息,这里可能被阻塞。
Message msg = queue.next(); // might block
// 如果返回了null,那么就说明MessageQueue已经quit了
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// ...... 日志输出等
try {
// 调用msg的Handler对象的dispatchMessage()去执行消息。
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
// ...... 日志输出等
// 将此msg放入回收对象池中,obtain()就是去复用这些对象。
msg.recycleUnchecked();
}
}
loop()不会自动退出,Looper#quit() 或者 Looper#quitSafely() 让它退出。两个方法都调用了MessageQueue#quit(boolean)方法,当MessageQueue#next()方法发现已经调用过 MessageQueue#quit(boolean)时会return null结束当前调用,否则的话即使MessageQueue 已经是空的了也会阻塞等待。
Q:既然是一个死循环,那为什么没有阻塞主线程呢?
如果说操作系统是由中断驱动的,那么Android的应用在宏观上可以说是 Handler机制驱动的,所以主线程中的 Looper不会一直阻塞的,原因如下:
- 当队列中只有延迟消息的时候,阻塞的时间等于头结点的 when 减去 当前时间,时间到了以后会自动唤醒。
- 在Android中 一个进程中不会只有一个线程,由于 Handler 的机制,导致我们如果要操作 View 等都要通过 Handler 将事件发送到主线程中去,所以会唤醒阻塞。
- 传感器的事件,如:触摸事件、键盘输入等。
- 绘制事件:我们知道要想显示流畅那么屏幕必须保持60fps的刷新率,那绘制事件在入队列时也会唤醒。
更多的知识Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
七、获取下一个消息MessageQueue的next()
MessgaeQueue # next()
这个方法可能是Handler机制中最长知识点最多的方法了,被上面的loop()调用,来获取下一个Message对象。
- 处理已经取消的情况。
- 进入循环调用nativePollOnce(),如果没有消息,就阻塞,如果有消息就继续。
- 获取第一个消息,如果第一个消息是barrier,就遍历去获取第一个异步消息。
enqueueMessage()会判断target不为空,barrier消息是在postSyncBarrier()中添加的,而异步消息是在Handler构造函数中指定的该Handlerf发送的消息是否为异步的。
- 如果消息时间到了就整理队列并返回该消息,如果每到就更新时间点。
- 如果此为第一遍循环,就去执行idleHandler。执行完之后复制变量让下一次循环不再执行此条。
Message next() {
// 处理Looper已经取消的情况,调用disposed()方法后mPtr=0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// 记录空闲时需要处理的IdleHandler的数量。
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 表示距离处理下一个消息的时间,只要大于0就表明还有消息等待处理
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
// 刷新Binder命令
Binder.flushPendingCommands();
}
// 调用native层,如果返回了就说明可以从队列中取出一条消息,如果消息队列中没有消息就阻塞等待
// 靠enqueueMessage()中最后一步调用nativeWake(mPtr)来唤醒该方法
nativePollOnce(ptr, nextPollTimeoutMillis);
// 上锁
synchronized (this) {
// 获取开机到现在的时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// 表头第一个消息
Message msg = mMessages;
// 判断该Messagehi否是barrier
if (msg != null && msg.target == null) {
// 循环遍历出第一个异步消息,如果设置了barrier,就不能再执行同步消息了,除非将barrier移除。
// 但是异步消息不受影响照样执行,所以在这里要找到异步消息
do {
prevMsg = msg;
msg = msg.next;
// msg为null说明已经退出循环,为异步消息则消息为要找的
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// 如果分发时间还没到
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
// 更新执行时间点
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 如果时间到了
// Got a message.
mBlocked = false;
// 处理消息队列
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
// 标记msg正在使用
msg.markInUse();
return msg;
}
} else {
// 进此处是msg==null,再没有其它消息了
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
// 正在退出了,返回null。
if (mQuitting) {
dispose();
// 返回null,通知looper也停止
return null;
}
// 判断如果这是第一次循环(只有第一次循环时会小于0)并且队列为空或还没到处理第一个的时间
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
// 置为阻塞状态
mBlocked = true;
continue;
}
// 初始化最少四个要被执行的IdleHandler
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 开始循环执行所有的IdleHandler并根据返回值判断是否保留
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);
}
}
}
// IdleHandler只会在消息队列阻塞之前执行一次,之后再不会执行,知道下一次被调用next()。
pendingIdleHandlerCount = 0;
// 当执行了IdleHandler后,会消耗一段时间,刺死可能已经到达执行消息的时间了,所以重置该变量再重新检查时间。
nextPollTimeoutMillis = 0;
}
}
八、Handler的dispatchMesssage()
在Looper的loop()中会调用该方法去处理msg,其实是一个分发过程。
msg.target.dispatchMessage(msg);
这里调用了target的方法,target字段是在Handler的enqueueMessage()中赋值的,为其Handler对象。这里调用了dispatchMessage()。
- 首先看msg中有没有callback对象,如果有,就交给callback执行。
- 如果没有再看有没有全局callback对象,如果有,就交给全局callback处理。
- 如果都没有或者全局callback处理不了,再调用handleMessage()。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里的一系列优先级让我想起了View的事件分发。
首先判断的msg.callbck是调用post系方法时传入的runnable,这个执行的优先级最高。
private static void handleCallback(Message message) {
message.callback.run();
}
再就是判断mCallback,这个callback是在某些带有Callback参数的Handler构造函数中传入的。
public Handler(Callback callback, boolean async) {
// ......
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;
}
当然了,这个runnable完全可以返回false,设计在个人,如果想让这个msg还有机会交给Handler的handleMessage()去处理,就让它在合适的情况返回false。
最终到达handleMessage(),这个优先级最低了。
public void handleMessage(Message msg) {
}
我们最常用的就是在定义Handler对象时重写这个方法添加自己的逻辑。