一:概念简要
Handler:主要作用是将一个任务切换到指定线程(即Handler所在的线程)中去执行。比如子线程中获取到数据,到主线程中修改UI;主要用来解决子线程无法访问UI的问题。
MessageQueue:是消息队列,内部存储了一组消息,以队列的形式对外提供插入和删除的工作;
Looper:循环,MessageQueue只是一个消息存储单元,并不能处理消息。所以需要Looper无限循环的形式去查找是否有新消息,然后对信息进行处理;
ThreadLocal:并不是线程,他的作用是在每个线程存储数据。Handler创建的时候会采用当前线程的Looper来构造消息循环系统,获取消息。那么怎么获取当前线程的Looper呢?使用ThreadLocal。ThreadLocal可以在不同线程中互不干扰的存储并提供数据,所以通过ThreadLocal能够轻松的获取每个线程的Looper。注意,线程是没有默认Looper的,如果需要使用Handler就必须为线程创建Looper。主线程即UI线程,也就是ActivityThread,ActivityThread被创建时会初始化Looper,这也就是主线程默认可以使用Handler的原因。
二:Handler工作原理
2.1:sendMessage相关部分源码及解释
通过Handler的sendMessage方法发送一个消息,会先后调用sendMessage —>sendMessageDelayed —>sendMessageAtTime —>enqueueMessage,最后调用MessageQueue的enqueueMessage()方法把这个消息加入到消息队列中
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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) {
//注意,在这里把当前Handler赋值给msg.target,之后在loop方法中会用到msg.target;
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
2.2:MessageQueue的enqueueMessage()
通过这个方法把消息添加到消息队列,这里要注意一个方法nativeWake(mPtr),下文中会讲解。
boolean enqueueMessage(Message msg, long when) {
/...省略代码/
synchronized (this) {
/...省略代码/
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的
msg.next = p;
mMessages = msg;
needWake = mBlocked; //当阻塞时需要唤醒
} else {
//将消息按时间顺序插入到MessageQueue。
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;
prev.next = msg;
}
//消息没有退出,我们认为此时mPtr != 0
//如果在阻塞中,需要调用nativeWake进行唤醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
2.3:Looper.loop()
获取MessageQueue,然后无限循环读取消息队列的消息,然后执行msg.target.dispatchMessage(msg);进行消息分发。
前文源码Handler的enqueueMessage()中说到:在Handler的enqueueMessage()中,把当前handler赋值给了msg.target;
所以 msg.target.dispatchMessage(msg);即调用Handler.dispatchMessage()方法,然后调用handleMessage方法,我们复写handleMessage自定义实现事件处理。
部分源码:
public static void loop() {
/...省略代码/
final MessageQueue queue = me.mQueue;
/...省略代码/
for (;;) {
Message msg = queue.next(); // might block(可能阻塞)
if (msg == null) {
// No message indicates that the message queue is quitting.(没有msg表明消息队列正在退出)
return;
}
/...省略代码/
//msg.target为handler,调用handler分发msg
msg.target.dispatchMessage(msg);
}
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Message的next(),这里主要讲解为什么一直处于死循环却不耗费cpu资源,因为调用了nativePollOnce方法。故删除了很多源码
Message next() {
final long ptr = mPtr;
//当消息循环已经退出,则直接返回null,loop的死循环会直接return;
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);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
/...省略代码/
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.markInUse();
//返回MessageQueue中的下一条即将要执行的消息
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
/...省略代码/
}
/...省略代码/
}
}
主线程的死循环一直运行是不是特别消耗CPU资源呢?
这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里。此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
那什么时候唤醒呢?
上文中我们提到在enqueueMessage()方法中,nativeWake可以唤醒loop;
总结:
Handler通过sendMessage()发送Message到MessageQueue队列【这个时候会调用MessageQueue.enqueueMessage()方法把消息添加到消息队列,如果loop此时处于阻塞状态,则需要调用nativeWake进行唤醒,往消息队列添加Message时,需要根据mBlocked情况来决定是否需要调用nativeWake】;之后Looper通过loop(),无限循环获取消息队列的Message【loop方法,会调用MessageQUeue中的next方法获取message,如果消息队列中没有消息,则会因为nativePollOnce这个方法让loop阻塞】,并将Message交给target来处理;msg.target即Handler,然后调用Handler的dispatchMessage()方法,之后调用Handler的handleMessage()来进行消息处理。
拓展:常见问题总结
1.主线程中为什么不能执行耗时操作呢?
手机显示的刷新频率是 60Hz,也就是一秒钟刷新 60 次,每 16.67 毫秒刷新一次,为了不丢帧,那么主线程处理代码最好不要超过 16 毫秒。
2.子线程为什么不能访问UI呢?
因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI处于不可预期的状态。为什么不对UI控件添加锁机制呢?
原因有二:1.加上锁机制会让UI访问逻辑复杂;2.会降低UI的访问效率,因为锁机制会阻塞某些线程的运行。
子线程访问UI,有checkThread方法,验证当前线程是否主线程,不是会抛出异常。
3:主线程的Looper死循环为什么不会导致应用卡死,会消耗大量资源吗?
这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里。此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
那什么时候唤醒呢?上文中我们提到在enqueueMessage()方法中,nativeWake可以唤醒loop;
4:为什么主线程不需要创建Looper
1.Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper会报错【Can't create handler inside thread that has not called Looper.prepare()")】。主线程也就是ActivityThread,ActivityThread被创建时会初始化Looper;
ActivityThread入口main函数部分源码:
public static void main(String[] args) {
//创建Looper和MessageQueue对象,用于处理主线程的消息
Looper.prepareMainLooper();
//创建ActivityThread对象,建立Binder通道 (创建新线程)
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
//消息循环开启
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
5:Handler 是如何进行线程切换的呢?
Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在与他关联的Looper对应的线程中处理消息的。
那么Handler内部如何获取到当前线程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是①线程是默认没有Looper的,如果需要使用Handler,就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,②ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
6:子线程有哪些更新UI的方法。
1:主线程中定义Handler,子线程通过mHandler发送消息,主线程Handler的handleMessage更新UI。
2:用Activity对象的runOnUiThread方法。
3:View.post(Runnable r) 。
参考文章<Android开发艺术探索>