1、概述
在 Android 中,只有主线程才能操作 UI,但是主线程不能进行耗时操作,否则会阻塞线程,产生 ANR 异常,所以常常把耗时操作放到其它子线程进行。如果在子线程中需要更新 UI,一般是通过 Handler 发送消息,主线程接受消息并且进行相应的逻辑处理。除了直接使用 Handler,还可以通过 View 的 post 方法以及 Activity 的 runOnUiThread 方法来更新 UI,它们内部也是利用了 Handler。
很多人认为Handler的作用是更新UI,这的确没错,但是更新UI仅仅是Handler的一个特殊的使用场景。具体来说是这样的:有时候需要在子线程中进行耗时的IO操作,可能是读取文件或者访问网络等, 当耗时操作完成以后可能需要在UI上做一些改变,由于Android开发规范的限制,我们并不能在子线程中访问UI控件,否则就会触发程序异常,这个时候通过Handler就可以将更新UI的操作切换到主线程中执行。因此,本质上说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。
由于Android中的View是线程不安全的,然而程序中异步处理任务结束后更新UI元素也是必须的。这就造成了一个矛盾,最简单的解决方法肯定是给View加同步锁使其变成线程安全的。这样做不是不可以,只是会有两点坏处:
- 会让UI访问的逻辑变得复杂
- 会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
基于以上两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只是通过Handler切换一下UI访问的执行线程即可。
2、基本用法
step1:创建Handler实例
//1.自定义Handler类
static class CustomHandler extends Handler{
@Override
public void handleMessage(Message msg) {
//更新UI等操作
}
}
CustomHandler customHandler = new CustomHandler();
//2.内部类
Handler innerHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//更新UI等操作
}
};
//3.callback方式
Handler callbackHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//更新UI等操作
return true;
}
});
step2:发送消息
//1.发送普通消息
Message msg = Message.obtain();
msg.what = 0; //标识
msg.obj = "这是消息体"; //消息内容
innerHandler.sendMessage(msg);
//2.发送Runnale消息
innerHandler.post(new Runnable() {
@Override
public void run() {
//更新UI等操作,消息接收后执行此方法
}
});
3、源码解析
3.1 Handler中存在四种角色
Handler
Handler用来向Looper发送消息,调用sendMessage等多个方法,在Looper处理到对应的消息时,Handler在对消息进行具体的处理。上层关键API为handleMessage(),由子类自行实现处理逻辑。Looper
Looper运行在目标线程里,不断从消息队列MessageQueue读取消息,分配给Handler处理。Looper起到连接的作用,将来自不同渠道的消息,聚集在目标线程里处理。也因此Looper需要确保线程唯一。MessageQueue
存储消息对象Message,当Looper向MessageQueue获取消息,或Handler向其插入数据时,决定消息如何提取、如何存储。不仅如此,MessageQueue还维护与Native端的连接,也是解决Looper.loop() 阻塞问题的 Java 端的控制器。Message
Message包含具体的消息数据,在成员变量target中保存了用来发送此消息的Handler引用。因此在消息获得这行时机时,能知道具体由哪一个Handler处理。此外静态成员变量sPool,则维护了消息缓存池以复用。
这几个类的作用还可以用下图解释:
3.2 Handler
使用Handler之前,我们都是初始化一个实例,比如用于更新UI线程,我们会在声明的时候直接初始化,或者在onCreate中初始化Handler实例。所以我们首先看Handler的构造方法,看其如何与MessageQueue联系上的,它在子线程中发送的消息(一般发送消息都在非UI线程)怎么发送到MessageQueue中的。
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur");
}
}
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;
}
在①处通过Looper.myLooper()获取与创建Handler线程绑定的Looper,如果 mLooper 为空,那么会抛出异常:"Can't create handler inside thread that has not called Looper.prepare()",意思是:不能在未调用 Looper.prepare() 的线程创建 handler,然后在③处又获取与Looper绑定的MessageQueue,因为一个Looper只能有一个MessageQueue,也就是与当前线程绑定的MessageQueue,这样就保证了Handler的实例与我们Looper实例中MessageQueue关联上了。
到此,Android 消息机制的三个重要角色全部出现了,分别是 Handler
、Looper
以及 MessageQueue
。 一般在代码我们接触比较多的是 Handler ,但 Looper 与 MessageQueue 却是 Handler 运行时不可或缺的。
Handler发送消息最常用的方式就是sendMessage(),除此之外也可以通过post()、postAtTime()、postDelayed()等方法发送消息,这几个方法均会执行到sendMessageAtTime()方法。
下面以最常用的sendMessage方法为例
发送消息的方法
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 final boolean sendEmptyMessage(int what){
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
二者最终都会调用 sendMessageAtTime方法
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);
}
在sendMessageAtTime方法中,接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。
可以看到在①处为MessageQueue赋值mQueue,这个值是在Handler的构造方法中通过Looper获取,在获取MessageQueue然后调用了enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // ①
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); // ②
}
在该方法内的①处,首先为meg.target赋值为this,此处的this就是当前Handler对象本身。在这就指明了该msg的来源——它是由哪个Handler发出的,与此同时也指明了该msg的归宿——它该由哪个Handler处理。不难发现,哪个Handler发出了消息就由哪个Handler负责处理。
在②处,将消息放入消息队列中,距离触发时间最短的message排在队列最前面,同理距离触发时间最长的message排在队列的最尾端。若调用sendMessageAtFrontOfQueue()方法发送消息它会直接调用该enqueueMessage(msg,uptimeMillis)让消息入队只不过时间为延迟时间为0,也就是说该消息会被插入到消息队列头部优先得到执行。
接下来我们看MessageQueue中的enqueueMessage()方法,该方法就是消息入队操作了。这个MessageQueue是一个消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表。MessageQueue主要包含两个操作,插入和读取,分别对应enqueueMessage()方法和next()方法。
MessageQueue是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。
boolean enqueueMessage(Message msg, long when) {
// msg 必须有target也就是必须有handler
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) {
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();
//when 表示这个消息执行的时间,队列是按照消息执行时间排序的
//如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 这里三种情况:
// 1、目标消息队列是空队列
// 2、插入的消息处理时间等于0
// 3、插入的消息处理时间小于保存在消息队列头的消息处理时间
// 这三种情况都插入列表头
msg.next = p;
mMessages = msg;
//需要唤醒主线程,如果队列没有元素,主线程会堵塞在管道的读端,这时
//候队列突然有消息了,就会往管道写入字符,唤醒主线程
needWake = mBlocked;
} else {
// 这里则说明消息处理时间大于消息列表头的处理时间,因此需要找到合适的插入位置
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//将消息放到队列的确切位置,队列是按照msg的when 排序的
for (;;) {
prev = p;
p = p.next;
// 到链表尾,或处理时间早于p的时间
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
// 如果插入的消息在目标队列中间,是不需要检查改变线程唤醒状态的
needWake = false;
}
}
// 插入到消息队列
msg.next = p;
prev.next = msg;
}
if (needWake) {
// 唤醒线程
nativeWake(mPtr);
}
}
return true;
}
3.3 Looper
在创建 Looper 的时候,通过构造方法会创建一个消息队列,代码如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在Looper构造方法初始化时,就给当前线程创建了消息队列MessageQueue,并且让Looper持有MessageQueue的引用。所以在Handler构造方法里就可以为消息队列赋值mQueue = mLooper.mQueue;
上面分析了 Handler 的构造方法,其中调用了 Looper.myLooper() 方法,下面是它的源码:
static final ThreadLocal sThreadLocal = new ThreadLocal();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这个方法非常简单,就是从sThreadLocal对象中取出Looper。如果sThreadLocal中有Looper存在就返回Looper,如果没有Looper存在自然就返回空了。
在 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()");
}
...
}
每个线程想要获得 Looper 需要调用 prepare() 方法,如下所示:
public static void prepare() {
prepare(true);
}
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));
}
可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper保存到ThreadLocal里面。不过需要注意的是如果 sThreadLocal 已经设置过了,那么会抛出异常,也就是说一个线程只会有一个 Looper。
这里涉及到ThreadLocal的知识点!ThreadLocal是什么?它是一个用来存储数据的类,类似HashMap、ArrayList等集合类。它的特点是可以在指定的线程中存储数据,然后取数据只能取到当前线程的数据。
举个例子:
ThreadLocal mThreadLocal = new ThreadLocal<>();
private void testMethod() {
mThreadLocal.set(0);
Log.d(TAG, "main mThreadLocal=" + mThreadLocal.get());
new Thread("Thread1") {
@Override
public void run() {
mThreadLocal.set(1);
Log.d(TAG, "Thread1 mThreadLocal=" + mThreadLocal.get());
}
}.start();
new Thread("Thread2") {
@Override
public void run() {
mThreadLocal.set(2);
Log.d(TAG, "Thread1 mThreadLocal=" + mThreadLocal.get());
}
}.start();
Log.d(TAG, "main mThreadLocal=" + mThreadLocal.get());
}
输出的log是
main mThreadLocal=0
Thread1 mThreadLocal=1
Thread2 mThreadLocal=2
main mThreadLocal=0
通过上面的例子可以清晰的看到ThreadLocal存取数据的特点,只能取到当前所在线程存的数据,如果所在线程没存数据,取出来的就是null。ThreadLocal保证每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用。
好了回到正题,prepare()创建Looper的时候同时把创建的Looper存储到了ThreadLocal中,通过对ThreadLocal的介绍,获取Looper对象就很简单了,sThreadLocal.get()即可,源码提供了一个public的静态方法可以在主线程的任何地方获取这个主线程的Looper(注意一下方法名myLooper(),多个地方会用到):
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
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.");
}
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;
}
try {
msg.target.dispatchMessage(msg); // ⑤
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
msg.recycleUnchecked(); // ⑥
}
}
在①处首先获取Looper,然后在②处判断如果Looper为空,说明在线程中必须关联一个Looper。在③处通过Looper获取消息队列,④处开始一个死循环,通过调用消息队列的next()方法queue.next()
进行出队操作。 也就是说在该段代码中Looper一直在轮询消息队列MessageQueue。假若消息队列中没有未处理的消息(即queue.next()==null)则其进入阻塞状态,假若消息队列中有待处理消息(即queue.next()!=null)则利用⑤处msg.target.dispatchMessage(msg)
将该消息派发至对应的Handler。最后⑥处将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。
到了这,可能有的人会有一个疑问:系统怎么知道把消息发送给哪个Handler呢? 其实就是在enqueueMessage()中系统给msg设置了target从而确定了其目标Handler,所以只要通过msg.target.dispatchMessage(msg)
就可以将消息派发至对应的Handler了。那在dispatchMessage()中又会对消息做哪些操作呢?我们继续跟进源码。
在看dispatchMessage()方法之前先看一下queue.next() 的方法的源码(主要还是看注释):
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
// 获取NativeMessageQueue地址失败,无法正常使用epoll机制
return null;
}
// 用来保存注册到消息队列中的空闲消息处理器(IdleHandler)的个数
int pendingIdleHandlerCount = -1;
// 如果这个变量等于0,表示即便消息队列中没有新的消息需要处理,当前
// 线程也不要进入睡眠等待状态。如果值等于-1,那么就表示当消息队列中没有新的消息
// 需要处理时,当前线程需要无限地处于休眠等待状态,直到它被其它线程唤醒为止
int nextPollTimeoutMillis = 0;
for (;;) {
......
// 检查当前线程的消息队列中是否有新的消息需要处理,尝试进入休眠
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 当前时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// mMessages 表示当前线程需要处理的消息
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 找到有效的Message
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
/**
* 检查当前时间和消息要被处理的时间,如果小于当前时间,说明马上要进行处理
*/
if (now < msg.when) {
// 还没达到下一个消息需要被处理的时间,计算需要休眠的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 有消息需要处理
// 不要进入休眠
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();
return msg;
}
} else {
// 没有更多消息,休眠时间无限
nextPollTimeoutMillis = -1;
}
......
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// 获取IdleHandler数
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有IdleHandler需要处理,可直接进入休眠
mBlocked = true;
continue;
}
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数量
pendingIdleHandlerCount = 0;
/**
* 这里置0,表示下一次循环不能马上进入休眠状态,因为IdleHandler在处理事件的时间里,
* 有可能有新的消息发送来过来,需要重新检查。
*/
nextPollTimeoutMillis = 0;
}
}
继续跟进Handler的dispatchMessage()方法,代码如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先处理Message的回调callback,比如调用handler.post(Runnable runnable)
时,该runnable就会被系统封装为Message的callback。
然后处理Handler的回调callback,比如执行Handler handler=Handler(Callback callback)时就会将callback赋值给mCallback
最后调用handleMessage()处理消息Message。
The End!