Handler源码解析

因为 Android 只允许在主线程中更新 UI ,所以每个 Android 开发者都会使用到 Handler ,最近面试也一直在问这个问题。先说一下我自己的理解吧

每个 Activity 都会自动初始化一个 Looper 对象,这个 Looper 对象通过 loop() 方法,不断的遍历 MessageQueue,来查看消息队列里是否存在 Message,在代码里 在其他线程中可以通过 handler.sendMessage(message) 来把 Message 传到 MessageQueue 里,可以在 handler 重写的 handlerMessage(Message msg) 里获取到发送的 Message 来执行所要完成的动作。

从 Handler 的无参构造方法开始看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Handler() {
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 = null;
}

可以看到在第十行调用了 Looper.myLooper()方法来获取一个 Looper 对象,如果这个对象为空则抛出一个异常。看一下 myLooper() 方法

1
2
3
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}

这个方法非常简单,就是从sThreadLocal对象中取出Looper。如果sThreadLocal中有Looper存在就返回Looper,如果没有Looper存在自然就返回空了。因此你可以想象得到是在哪里给sThreadLocal设置Looper了吧,当然是Looper.prepare()方法!我们来看下它的源码:

1
2
3
4
5
6
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}

可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。

所以在非 UI 线程中创建 Handler 对象之前要先调用 Looper.prepare() 方法,否则会出现异常。而且在程序启动时,系统已经自动在主线程中调用了Looper.prepare() 方法。所以在主线程中我们可以直接新建 Handler 。

看完了如何创建Handler之后,接下来我们看一下如何发送消息,这个流程相信大家也已经非常熟悉了,new出一个Message对象,然后可以使用setData()方法或arg参数等方式为消息携带一些数据,再借助Handler将消息发送出去就可以了,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.arg1 = 1;
Bundle bundle = new Bundle();
bundle.putString("data", "data");
message.setData(bundle);
handler.sendMessage(message);
}
}).start();

可是这里Handler到底是把Message发送到哪里去了呢?为什么之后又可以在Handler的handleMessage()方法中重新得到这条Message呢?看来又需要通过阅读源码才能解除我们心中的疑惑了,Handler中提供了很多个发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都会辗转调用到sendMessageAtTime()方法中,这个方法的源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}

sendMessageAtTime() 方法接收两个参数,其中 msg 参数就是我们发送的 Message 对象,而 uptimeMillis 参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到 MessageQueue 的 enqueueMessage() 方法中。这个 MessageQueue 又是什么东西呢?其实从名字上就可以看出了,它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在 Looper 的构造函数中创建的,因此一个 Looper 也就对应了一个 MessageQueue。

那么enqueueMessage()方法毫无疑问就是入队的方法了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}

首先你要知道,MessageQueue 并没有使用一个集合把所有的消息都保存起来,它只使用了一个 mMessages 对象表示当前待处理的消息。然后观察上面的代码的 16~31 行我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的 uptimeMilli 参数。具体的操作方法就根据时间的顺序调用 msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把 mMessages 赋值为新入队的这条消息,然后将这条消息的 next 指定为刚才的 mMessages,这样也就完成了添加消息到队列头部的操作。
现在入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?这个就需要看一看Looper.loop()方法的源码了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
msg.recycle();
}
}
}

可以看到,这个方法从第4行开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。不过由于这个方法的代码稍微有点长,我就不贴出来了,它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法的第14行,每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法的第6行就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

在第5行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!

另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:

  1. Handler的post()方法

  2. View的post()方法

  3. Activity的runOnUiThread()方法

Handler中的post()方法,代码如下所示:

1
2
3
4
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}

原来这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将 Runnable 对象转换成了一条消息,我们来看下这个方法的源码:

1
2
3
4
5
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

在这个方法中将消息的 callback 字段的值指定为传入的 Runnable 对象。咦?这个 callback 字段看起来有些眼熟啊,喔!在 Handler 的dispatchMessage()方法中原来有做一个检查,如果 Message 的 callback 等于 null 才会去调用handleMessage()方法,否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧:

1
2
3
private final void handleCallback(Message message) {
message.callback.run();
}

再来看一下View中的post()方法,代码如下所示:

1
2
3
4
5
6
7
8
9
10
public boolean post(Runnable action) {
Handler handler;
if (mAttachInfo != null) {
handler = mAttachInfo.mHandler;
} else {
ViewRoot.getRunQueue().post(action);
return true;
}
return handler.post(action);
}

原来就是调用了Handler中的post()方法,我相信已经没有什么必要再做解释了。
最后再来看一下Activity中的runOnUiThread()方法,代码如下所示:

1
2
3
4
5
6
7
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。

线程是怎么切换的?

当 A 线程创建 Handler 的时候,同时创建了 Looper 和 MessageQueue,Looper 随即在 A 线程中调用 loop() 方法,而有因为在 loop() 方法中从 MessageQueue 取出消息并进行下一步的操作,所以操作又回到了 A 线程中。

参考文章:Android Handler、Message完全解析,带你从源码的角度彻底理解

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×

keyboard_arrow_up 回到顶端