很早以前,就研究过Handler机制源码,但当时并没有用文字记录。以至于工作中被同事、同行问起,只能回忆起大概,也就不好意思说自己懂它了。正好今天没事儿,重新撸一撸源码,以来丰富博客,而来说不定有新的收获呢!
现在的手机屏幕的刷新频率基本为60Hz,也就是一秒撸60次(这手速!),平均16.67毫秒/次。所以想要APP所有的内容都显示出来,也就是不丢帧,就得保证主线程执行时长不超过16毫秒。所以,超过这个时常的逻辑,就别放到主线程了。即使一个刚入Android坑的同学,也知道IO读写,网络等耗时逻辑必须放在子线程中处理,相信你也不会在这上边犯错。
那么,问题来了,主线程通常也被称为UI线程,它的作用就是用来创建并且更新UI的。我们在子线程中逻辑执行的结果,比如我们从网络上获取一张图片之后,怎么让主线程去显示这张图片呢?没错,Android提供了Handler来解决线程之间的通信问题。
创建Handler对象,并复写handleMessage方法
Handler mHandler = new Handler() {
//创建一个Handler
@Override
public void handleMessage(Message msg) {
//复写handleMessage方法
//处理对应逻辑
}
};
发送消息给刚才创建的mHandler的对象处理。发送过后,handleMessage函数将被调用。这就是一次简单的Handler的调用。
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessage(message);
走进源码
接下来,我们就围绕Handler、Message、Looper以及MessageQueue来探索Handler中的奥秘吧。
首先,来看看mHandler.sendMessage(message)
做了什么。
代码一路跟踪,最终到了Handler中的一个函数
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(本文中也会称为‘消息队列’)也就是queue, 从if条件判断可以看出,queue在这之前必然已经初始化(别问为什么)。既然如此,我们来开个小差,看看给queue赋值的mQueue什么时候初始化的。代码依然在Handler中。
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: " +
klass.getCanonicalName());
}
}
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;
}
擦,捅了马蜂窝了,代码这么多。我就看个初始化而已,至于吗。哎,我们继续。这是Handler的构造函数之一,我们的
new Handler()
会调用该构造完成对象创建动作。在该构造函数中,我们看到了我们的目标mQueue得到了初始化mQueue = mLooper.mQueue;
,好吧,mLooper开始出来搞事情了。可以看出,mLooper是通过Looper对myLooper函数静态调用赋值的mLooper = Looper.myLooper();
。进入Looper代码。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
进一步,我们发现,myLooper函数的返回值实际上是通过
sThreadLocal.get()
获得的线程本地变量副本。所以,该变量副本肯定是通过sThreadLocal.set();
赋值的(关于ThreadLocal原理大家可自行百度)。代码依然在Looper中,查看代码,果然。
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
看到没
sThreadLocal.set(new Looper(quitAllowed));
说什么来着。代码看到这里,不知道大家还记不记得,跟踪到这里是为啥。是为了查看mQueue的初始化过程。
简单梳理一下。
- 在Handler.sendMessage的调用链中,我们看到了发送消息之前,有一个叫queue的MessageQueue必须初始化。
- queue的初始化
mQueue = mLooper.mQueue;
,mLooper = Looper.myLooper();
引出了Looper。Looper.myLooper()
的返回值,实际上是ThreadLocal中保存的Looper变量副本。- 结合上面一段代码,可以看出,Looper变量副本的保存,是通过调用Looper.prepare()保存的。调用的同时,会新建一个Looper对象。而创建Looper对象的构造函数时,new了一个MessageQueue对象并且保存到了变量mQueue中。这个mQueue正式我们最开始发现的消息队列。
好了,代码看到这里,我们只需要知道Looper.prepare()方法在哪里调用就能完成这一步的探索。通过搜索源码,ActivityThread.java。可以看到如下代码
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
这部分代码片段来自于Nougat 7.1.1_r6,可以看出Activity主线程的主函数中,调用了
Looper.prepareMainLooper();
,看起来是不是很像Looper.prepare()?大胆猜测一下,就是这个函数。代码回到Looper,找到prepareMainLooper();
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
果然prepareMainLooper()中调用了prepare()方法。至此MessageQueue的出事话过程就全明白了。
1. MessageQueue在UI线程中,早已经通过调用Looper.prepareMainLooper()
创建了一个Looper对象,同时也创建了一个MessageQueue对象并使用变量mQueue保存在该Looper中。明白消息队列初始化过程,是一个很好的开始。
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);
}
还记得最开始贴出的代码吗,对,就是Handler.sendMessage()引出的。这段代码,还有一个比较关键的是
enqueueMessage()
,字面意思是加入消息队列。我们可以大胆猜测,这个函数就是要将发送的消息,加入到之前初始化的消息队列中。来看代码。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
简单来看,该函数做了两件事情。
msg.target = this;
使当前发送的消息对象msg.target保持对当前Handler对象的引用。这一部分对于Handler机制非常重要。- 调用之前的消息队列
queue.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过程中,不知道大家有没有想过这样一个问题,Handler机制中有一个消息队列,里边可以存放很多消息对象。那么就必然需要用一种数据结构来维护,那这是个什么样的数据结构呢?我刚开始猜测不是数组,就是集合。于是,我就在源码里拼命的找啊,结果屁都没找到。显然,GooGle大神们想的和我等屁民并不一样。代码就在上边,为了突出,专门贴出来,见识一下吧。
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;
}
这段代码其实很简单。假设现在有三条消息msg1,msg2,msg3。这段代码将这三条消息彼此关联起来,形成了一种单向链表的结构。
- msg1 是队首消息,应该本第一个发送,或者说第一个轮询。
- msg1.next = msg2; msg1.next记录了下一个将要轮询的消息msg2。
- msg2.next = msg3; msg2.next记录msg2的下一个需要轮询的消息为msg3。
大神就是大神,我等屁民只好学着点了。代码非常简单,怕就怕“我怎么想不到”。
好了,代码就看完了……欸,不对。初始化了Looper,MessageQueue,也通过enquenMessage将消息加入到了队列中。然后呢?消息是怎么发送到handlerMessage中的呢?
细心的同学应该发现,在ActivityThread的主函数中,调用Looper.prepareMainLooper后,接着就调用了Looper.loop()(可以回去查看一下这部分代码);那么是不是,prepare之后就必须调用loop呢?是的,在prepare函数的源码注释中可以看到:Be sure to call * {@link #loop()} after calling this method。可见loop()函数是Handler机制中不可或缺的一环。那么就来看看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();
}
}
我们来细细读一下这段代码(去除了和本文主题无关部分)。
*Message msg = queue.next();
获取消息链表中的队首消息。next()函数就不多介绍了,和入队时形式差不多,不过这个方法是从消息队列中获取message。
*msg.target.dispatchMessage(msg);
调用enqueueMessage方法时,msg.target记录的Handler对象,调用其dispatchMessage函数。来看看这个函数。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
代码显而易见,直接调用了handleMessage函数,这就是为啥我们创建Handler时需要重写handleMessage方法的原因。
最后,总结一下。Message:
既是消息对象,也是消息队列的重要组成部分。前一个Message.next记录了后一个Message对象。以此为基础形成了一种类似链表的数据结构。
MessageQueue:
消息队列。自己并不是队列本身,更像是Message组成队列的一个维护者。它通过enqueueMessage(),以及next()函数控制消息的入队和出队达到维护的目的。
Handler: 有两个比较重要的方法。
sendMessage(): 发送的消息实际上只是被加入了Message的队列中。同时发送的消息对象会通过Message.target记录调用者,也就是调用该函数的Handler本身。
handleMessage(): 要想用Handler,这个函数必备重写。因为Looper在轮询消息队列时会通过Message.target.handleMessage(),调用这个函数,达到线程间消息通讯的目的。
Looper:
Looper在UI线程中会默认调用Looper.prepareMainLooper();该函数的调用,会创建一个Looper对象,以及一个MessageQueue对象。MessageQueue对象会保存在Looper对象的mQueue字段中,方便调用。
Looper.prepareMainLooper()调用之后,接着就会调用Looper.loop()。该函数主要从Message队列中通过for循环不断的获取消息,且不断的调用dispatchMessage最终调用到handleMessage。达到线程间通讯的目的。
附送代码,不解释
public class HandlerActivity extends Activity{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handler_activity);
thread.start();
}
public void send(View view) {
//Button的点击事件
threadHandler.sendEmptyMessage(1);
}
Handler threadHandler;
Thread thread = new Thread() {
@Override
public void run() {
Looper.prepare();
threadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(HandlerActivity.this, "88888", Toast.LENGTH_SHORT).show();
}
};
Looper.loop();
}
};
}