Android中Handler解析
了解Handler
1、什么是Handler
Handler是android消息传递处理机制中的非常重要的一个组件,常用于工作线程对UI线程的更新和传递消息,实现线程间的通信。
2、为什么要使用Handle
我们日常开发中经常会涉及到多线程,那么多线程如何去更新UI呢,android系统要求非UI线程不能更新UI,否则会抛出异常 Only the original thread that created a view hierarchy can touch its views,相关逻辑可以参考ViewRootImpl源码,那么子线程如果想要更新UI只能通过某种方式去通知UI线程,所以这个方式就是使用Handler,它能够保证多线程并发更新UI,同时也保证了线程安全。
3、Handler相关概念
了解Handler之前首先先看一下下面几个和Handler相关的概念,什么是主线程,什么是工作线程?
主线程:当应用启动的时候,在入口类ActivityThread里会创建UI线程,用来处理ui相关事件。
子线程:执行耗时操作需要创建子线程。
Message:消息,线程之间通信的数据单元。
Message Queue:消息队列,存放消息Message的队列,先进先出。
Looper:循环器,消息队列MessageQueue和Handler的通信媒介。循环取出Message Queue的消息,将消息发送给对应的Handler。
Handler:处理者,主线程和工作线程之间通信的媒介,消息的处理者,添加消息到Message Queue;处理Looper分发过来的消息。
那么,下面的问题你能答上来几个,
1)每个线程能拥有多个Looper么?
2)一个Looper可以绑定多个Handler么?
3)主线程为什么不用初始化Looper,而子线程需要?
4)Looper循环取出消息为什么没有阻塞主线程,如何把消息发给对应的线程?或者说怎么做到线程切换的?
如果你还不会,接着看下去。。。。
Handler机制流程
整个流程分为几步:
1)准备阶段:
- 创建Looper对象: 应用启动会默认在主线程创建Looper,并调用Looper.loop(),所以主线程是默认有Looper的,但是子线程的话,需要自己手动创建Looper 并调用loop()。
- 初始化消息队列:Looper创建的时候,需要创建当前线程的Message Queue,和Looper一一对应。
- 创建Handler对象:通过构造方法创建Handler对象,无参构造方法和当前线程Looper绑定,当前线程没有Looper对象则会抛出异常Can’t create handler inside thread xx that has not called Looper.prepare(),所以如果在子线程创建Handler必须要先初始化Looper或者绑定主线程Looper,这个后面源码详细分析。
至此,Hander对象绑定了线程Looper和Message Queue。
2)发送消息:工作线程可以通过Handler发送消息到消息队列。
3)消息循环:Looper循环取出队列的消息,如果没有可以取的消息,则阻塞,否则取出消息发送给创建该消息的Handler。
4)处理消息:Handler接收Looper发送来的消息,并进行相关的业务逻辑处理。
至此,一个完整的消息处理流程就完成了。流程图如下:
Handler的使用
Handler使用也非常简单,分为三步,创建对象、发送消息、接收消息
1)创建对象:
查看Handler源码可以发现,Handler的构造方法有7个,可以分为两大类,第一类:绑定当前线程Looper,在Handler构造方法里直接获取到当前线程的Looper,如果是主线程那么不用初始化Looper,如果是子线程,那必须先调用Looper.prepare()创建当前线程Looper对象,然后和Handler对象绑定,否则就会抛异常:
Handler handler = new Handler(Looper.getMainLooper());
第二类,绑定传入的Looper,这个Looper可以是传入的主线程Looper,也可以是子线程创建的Looper。
2)发送消息:
Handler支持发送即时消息和延时消息,实现原理后面源码会介绍:
handler.sendMessageDelayed(Message.obtainMessage(), 1000);
或者post方式
handler.post(Runnable r);
3)接收消息:
通过handleMessage()回调来接收消息,更新UI等。如果使用Runnable,则在Runnable回调里处理。
@Override
public void handleMessage(Message msg) {
//更新UI
}
或者post回调
post(new Runnable() {
@Override
public void run() {
//更新UI
}
});
源码解析
下面主要分析一下handler、Looper、MessageQueue源码中的实现。
1、Handler
1)构造方法:
绑定Looper、MessageQueue
2)发送消息:
2、Looper
1、Looper的成员变量:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue; //一个MQ
final Thread mThread; //一个thread
2、使用
首先需要先初始化,调用Looper.prepare();
然后开启循环,Looper.loop();
注:主线程默认是已经帮我们处理上面两步,如果是要发送到主线程Looper,不需要关心上面两点。如果是子线程,则需要自己实现上面两步。
3、prepare分析
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//每个线程只能有一个looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//设置本线程的looper
sThreadLocal.set(new Looper(quitAllowed));
}
注:prepare(boolean)方法中,有一个sThreadLocal变量(ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本),这个变量有点像一个哈希表,
它的key是当前的线程,也就是说,它可以存储一些数据/引用,这些数据/引用是与当前线程是一一对应的,在这里的作用是,
它判断一下当前线程是否有Looper这个对象,如果有,那么就报错了,“Only one Looper may be created per thread”,一个线程只允许创建一个Looper,如果没有,就new一个。然后它调用了Looper的构造方法。
4、Looper 的构造方法
在上边调用了构造方法:Looper(quitAllowed),初始化了messageQueue并绑定当前线程。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
此时的初始化动作已经结束了,接下来看Looper.loop():
5、loop()方法解析
public static void loop() {
final Looper me = myLooper();//返回当前的looper
if (me == null) {
//looper必须先调用prepare()
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//取得messageQueue
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
注:
1)loop()方法里需要获取到当前线程对应的Looper对象,就用的下面的这个myLooper(),
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
2)MessageQueue queue = me.mQueue;
拿到与这个Looper对应的MQ,就开始无限循环,不断的从消息队列中取出Message,当获取到一个消息后,它调用了Message.target.dispatchMessage()方法来对消息进行处理。dispatchMessage这个方法在Handler中,就是处理message。
3)msg.recycleUnchecked(),当这个msg处理完了,就没有用啦,把它回收到全局池中,注意不是销毁。
3、MessageQueue
1、成员变量
private final boolean mQuitAllowed;//表示MessageQueue是否允许退出
private long mPtr; //mPtr是native代码相关的
Message mMessages; //表示消息队列的头Head
2、构造方法
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed; //true是允许推出
mPtr = nativeInit(); //这是一个本地方法
}
MQ的构造方法简单的调用了nativeInit()来进行初始化,这是一个jni方法,也就是说,可能是在JNI层维持了它这个消息队列的对象。在message中有好多native方法,可以看出message是比较底层的一个类。
3、next()方法
Message next() {
final long ptr = mPtr;
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 && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} 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.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
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;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
注:
首先初始化2个接下来要用到的变量,紧接着进入无限for循环中,其某次循环主要做这么几件事情:
1) 如果nextPollTimeoutMillis != 0的话,调用Binder.flushPendingCommands();
2) 调用nativePollOnce(mPtr, nextPollTimeoutMillis);
3) 进入一个大的同步块,尝试获取一个可以处理的消息,具体做法是,记录当前时间now,初始化变量prevMsg为null,msg为mMessges;
如果msg是一个sync barrier消息,则直奔下一个asynchronous消息(这之间的所有同步消息会被本次循环忽略,也就是说遇到这种情况,
next方法会从找到的异步消息的位置开始尝试获取一个可以处理的消息并返回),同时更新prevMsg,msg的值;
4)当退出此do…while循环的时候msg可能为空(走到队列尾了),或者成功找到了一个这样的(异步)消息。
5)如果是到队尾了即msg==null,则表示没更多的消息了,设置nextPollTimeoutMillis = -1;否则当now<msg.when(msg的时间还没到),设置一个合理的等待时间,即调用
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
6)当msg到了该处理的时间了,也就是说我们找到了这样一个消息可以返回了,设置mBlocked为false,将msg从mMessages队列中取出来(类似单链表的删除操作),并执行
msg.next=null、msg.markInUse(),返回msg。
7)如果到这一步了还没return的话,那说明还没有可以处理的消息,检查下队列是否要求退出了,如果是执行dispose(),返回null。当Looper的loop方法看到null的message的时候会退出loop。
8)接下来既然没消息可以处理,那就该处理IdleHandler了。如果pendingIdleHandlerCount小于0(注意其在第一次进入for循环是被初始化为-1)且没更多的消息需要处理,设置pendingIdleHandlerCount=mIdleHandlers.size();
9)如果pendingIdleHandlerCount还是<=0的话,表示没有idle handler需要执行,
设置mBlocked为true,接着进入下次循环。
10)接下来就是根据mIdleHandlers来初始化mPendingIdleHandlers。退出同步块后我们就剩下最后一件事了,那就是run Idle handlers。一个for循环用来做这就事情,在循环内如果IdleHandler没必要保留,则会从mIdleHandlers中移除。
11)最后重置pendingIdleHandlerCount为0(也就是4只会在第一次循环的时候执行一次),将nextPollTimeoutMillis设为0,因为当我们在
执行4的时候,新的Message可能已经到来了,所以我们需要立即开始(不需要等待)下次循环来检查。
4、enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
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();
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;
}
在后边介绍Handler的时候,可以看到,许多跟message(包括runnable)相关的操作,最终都delegate给了MessageQueue的enqueue方法。
和任何方法一样,在enqueue之前,也都是对参数msg的检查,比如msg如果在使用中,或者msg的target是null,都会抛出AndroidRuntimeException,进行完条件检查后,会进入真正的处理逻辑。接下来的操作类似在一张单链表中插入一个元素:进入同步块
1)如果此队列处于正在退出的状态则不能在往里入队了,不能插入元素了,在这种情况下会抛出RuntimeException,然后return false,
表示失败了;
2)接下来表示队列的状态ok,设置msg的when字段,临时变量p指向队列头;(必要的初始化,准备工作)
3) 如果队列是空的或when==0或when<p.when,也就是说要插入的这个message应该在第一个位置也就是队首,那么它将是新的Head,将它和原先的队列连接起来;
4)否则插入将发生在队列中间的某个位置(有可能是队尾),将msg插在第一个p的前面,p满足这个条件(p == null || when < p.when)。
最后退出同步块,返回true,表示操作(入队)成功。
5、quit()
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
注:quit方法根据所传参数safe的值,有2种不同的退出策略,如果是safe的退出,则执行removeAllFutureMessagesLocked(),其内部的逻辑为如果队首的元素还没到期那说明队列中其他所有的元素也都没到期,所以等同于删除所有的消息即调用
removeAllMessagesLocked();否则遍历队列找到第一个p.when>now这样的message元素p,(更新队列的尾部p.next=null,缩短队列)从p开始一直到队列结束都是要被删掉的元素,全部删除之;如果是unsafe的退出,则所有message都直接被删除并 回收即调用removeAllMessagesLocked()。
常见问题
1、为什么主线程的Looper是一个死循环,但是却不会ANR?
因为当Looper处理完所有消息的时候会进入阻塞状态,当有新的Message进来的时候会打破阻塞继续执行。这其实没理解好ANR这个概念。ANR,全名Application Not Responding。当我发送一个绘制UI 的消息到主线程Handler之后,经过一定的时间没有被执行,则抛出ANR异常。Looper的死循环,是循环执行各种事务,包括UI绘制事务。Looper死循环说明线程没有死亡,如果Looper停止循环,线程则结束退出了。Looper的死循环本身就是保证UI绘制任务可以被执行的原因之一。
2、Handler是如何切换线程的?
使用不同线程的Looper处理消息。代码的执行线程,并不是代码本身决定,而是执行这段代码的逻辑是在哪个线程,或者说是哪个线程的逻辑调用的。每个Looper都运行在对应的线程,所以不同的Looper调用的dispatchMessage方法就运行在其所在的线程了。
3、Handler的阻塞唤醒机制是怎么回事?
Handler的阻塞唤醒机制是基于Linux的阻塞唤醒机制。这个机制也是类似于handler机制的模式。在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。和Looper监听MessageQueue,Handler添加message是比较类似的。
4、什么是IdleHandler?
当MessageQueue为空或者目前没有需要执行的Message时会回调的接口对象。IdleHandler看起来好像是个Handler,但他其实只是一个有单方法的接口,也称为函数型接口:
public static interface IdleHandler {
boolean queueIdle();
}
在MessageQueue中有一个List存储了IdleHandler对象,当MessageQueue没有需要被执行的Message时就会遍历回调所有的IdleHandler。所以IdleHandler主要用于在消息队列空闲的时候处理一些轻量级的工作。
具体处理逻辑可以参考MessageQueue源码。