Handler的使用
- 直接上代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过构造器初始化一个handler对象
Handler handler = new MyHandler();
//使用handler发送一条空消息
handler.sendEmptyMessage(1);
}
private static class MyHandler extends Handler{
//handler处理消息
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
System.out.println("处理消息ing");
}
}
}
接下来对Handler进行分析
构造方法
这里默认会调用Handler的空参构造方法,下面是他的代码
public Handler() {
this(null, false);
}
这里他又调用了另一个构造方法
public Handler(@Nullable Callback callback, boolean async) {
...
//拿到线程对应的Looper
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 interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
他是一个接口,里面只有一个handleMessage方法,这里只是提供了另一种让handler处理消息的方式,意思
就是说,可以不采用先创建一个类继承Handler再重写handleMessage,然后再创建这个类的实例的方法,而是
可以直接在new Handler() 的时候传入一个Callback实例,通过callback的handleMessage也可以进行消息处理。
接着是第二个参数async,这个参数的意思应该是决定callback的回调方法要不要异步执行
回到构造方法中,主要做的事情就是拿到创建Handler所在线程所对应的Looper,并判断looper是不是null,由于handler发送的message需要添加到消息队列中,然后通过looper从消息队列中取出消息,将消息分派给各自的handler进行处理,因此这里必须先要有Looper,级looper不能为null,否则会抛出以下异常:
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
说明如果我们要在线程中使用handler,就要确保Looper.prepare()已经被调用
这里的Looper.prepare()实际上是对Looper进行初始化,可以跟进去看一下代码:
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的实例,这里使用ThreadLocal来保证当前线程只有一个Looper,ThreadLocal内部可以看做是一个键值对,键是当前线程,而值就是Looper实例。通过get()拿到当前线程对应的Looper,通过set()来设置当前线程的Looper,然后创建一个Looper实例放进去。这样就把Looper初始化好了
然后再来回到Handler的构造方法中,假如现在Looper已经被初始化好了,接下来handler就会将维护在looper中的消息队列保存下来。
mQueue = mLooper.mQueue;
sendEmptyMessage
- 上面我们已经分析完了handler的构造方法,拿到了handler实例,我们接下来使用了这个handler发了一条消息:
handler.sendEmptyMessage(1);
不只是这个方法,其他的什么post,sendMessage等方法最终都会调用到sendMessageAtTime这个方法中,下面就来看这个方法的实现
public boolean sendMessageAtTime(@NonNull 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);
}
这个方法的意思是发送一条消息并且这条消息需要在一段时间后执行,这里会先判断前面保存的消息队列是否为null,然后又会调用下面这个方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
...
return queue.enqueueMessage(msg, uptimeMillis);
}
在这里会将handler和消息对象进行绑定,这为后面派发消息时能够正确分发消息给不同的handler对象做的伏笔。最后会调用消息队列的入队方法将发送的消息加入到消息队列,这样Looper就可以取出消息了。
上面就完成了handler发送消息的步骤,接下来看看消息队列是怎么将消息入队的
在这之前,我们应该都要知道其实消息队列并不是队列,而是一个链表,每一个节点都是一个message对象,通过next属性连接到下一个节点,而节点之间是根据执行时间的升序排序的,越早执行的消息就排在队列前面
enqueueMessage
boolean enqueueMessage(Message msg, long when) {
...
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 {
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;
}
这个方法的内容比较多,但是逻辑是清晰的。
首先是第一个判断:
if (p == null || when == 0 || when < p.when)
这里的判断最终导致的结果是当前要插入的消息要不要直接插入到队列的头部,这是一个很关键的判断,这里涉及到了线程的休眠。
我们已经知道了Looper会不断的从消息队列中取出消息,而当消息队列没有消息或者当前消息还没到执行的时间时,就会将线程休眠。
那么什么时候来唤醒线程呢,是不是应该在插入消息的时候,如果消息插入到了头部,说明现在应该执行的是头部的消息,因为现在他的优先级更高,而不是前面导致线程休眠的消息。
因此这里特别的对插入头部进行判断,然后来看插入到队列头部的逻辑:
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 {
...
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);
}
这里根据执行前面的结果得到needWake的值,根据这个值来选择要不要唤醒线程。
这样消息插入就完成了,那么问题来了,插入到队列中的消息要怎么取出来然后派发给handler呢
这个工作要交给Looper来完成。之前说到了Looper的初始化,但是在初始化中只是创建了一个对象,并没有调用对象的任何方法,实际上在执行完Looper.prepare之后还要执行Looper.loop(),这样才能让Looper开始工作,而工作的重点自然就在loop()方法中,下面就来看看这个方法
loop( )
public static void loop() {
//这里说明调用loop之前一定要prepare初始化
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;
}
...
//拿到消息对应的handler进行消息派发
msg.target.dispatchMessage(msg);
...
}
}
这个方法很长,但是只需要关心looper从哪里取出消息,然后在哪里分发消息
首先取出消息是这样的:
Message msg = queue.next(); // might block
也就是说是通过调用消息队列的next方法拿到下一条消息,这里的注释说明了这个方法有可能会因为没有可执行消息而阻塞。
得到消息后要判断是否为null,这是退出loop死循环的条件,由于一般情况下消息队列会一直阻塞直到可以得到消息返回,因此这个条件一般都不会满足,在后面再来解释这个问题。
消息不为null,当然就是分发消息了,这里是这样分发消息的:
//拿到消息对应的handler进行消息派发
msg.target.dispatchMessage(msg);
这里的target就是在handler发送消息时设置的。
以上就是looper大概的工作
上面还提到了两个方法分别是queue.next()和target.dispatchMessage(msg),下面会对这两个方法再进行分析
queue.next()
Message next() {
...
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;
}
if (mQuitting) {
dispose();
return null;
}
...
}
...
nextPollTimeoutMillis = 0;
}
}
这个方法很长,其中有一个变量是需要关注的,首先是这个:nextPollTimeoutMillis,这个变量表示了距离下一条可执行消息的时长,其实就是当前时间到队列头结点执行时间的差值,如果这个变量不为0,说明目前还得不到可执行的消息,则需要进行线程休眠,从下面代码可以看出:
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
接下来首先拿到一条可以进行分发的消息,判断这条消息能不能执行,也就是看时间到了没有,如果没有,或者说没有可执行的消息,后面就会将线程阻塞,mBlock标志就会设置为true,否则,mBlock就会设置为false,表示没有阻塞线程,然后将消息返回,这个消息是非null的。
这里要需要知道的的地方是这个:
if (mQuitting) {
dispose();
return null;
}
如果当前线程正在退出的话,才会返回null,这就是looper停止工作的条件
next方法的逻辑大概是这样
然后再来看最后一个方法
dispatchMessage
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先这里对msg.callback进行判断,但是这个callback是怎么来的呢,其实当你使用post方法来post一个runnable对象时,内部会生产一个message对象并将这个runnable对象设置进去,所以这里的callback就是一个runnable对象,也就是你自己post进去的runnable对象。
如果这个callback不为null,就会执行他的run方法,否则就是看看handler自己的mCallback能不能用,之前也提到了,在创建Handler对象时可以传入一个Callback对象,它里面有一个handleMessage方法可以执行。
再最后就是会调用handler本身的handleMessage方法,这个方法默认是空实现的,因此需要通过继承Handler重写这个方法来完成自己的任务。
以上就是handler大致的工作流程以及Handler、Message、MessageQueue、Looper他们之间的关系了。