浅谈 Android 异步消息处理机制

文章目录
  1. 1. 参考文献

人生是一场马拉松,要认准目标,去稳步前行,切勿急躁,良好的节奏很重要。

不知大家发现没有,笔试或面试时,常会遇到这样一个问题,即让你谈谈对 Android 异步消息处理机制的理解。简要回答或许如下:首先,在主线程中创建一个 Handler 对象,并重写 handleMessage() 方法;然后,当子线程中需要进行 UI 操作时,就创建一个 Message 对象,并通过 Handler 发送出去该消息;之后,该消息会被添加到 MessageQueue 的队列中等待被处理,而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息;最后,消息分发回 Handler 的 handleMessage() 方法中。Handler 是在主线程中创建的,故此时的 handleMessage() 方法中的代码也在主线程中运行,这样,就可以安全地进行 UI 操作。

是的,UI 线程是不安全的,在子线程中进行 UI 操作,程序有可能会崩溃。接下来,深入探究下整个异步消息处理机制的原理。

我们都知道,Handler 是在主线程中创建的,实际上,子线程中也是可以创建的,不过在 new Handler() 句段上一行,要新增 Looper.prepare()。看一下 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;
}

第 10 行,调用了 Looper.myLooper() 方法获取了一个 Looper 对象,若对象为空,则会抛出一个运行时异常,提示为 Can't create handler inside thread that has not called Looper.prepare()。再看 Looper.myLooper() 中的代码:

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

从 sThreadLocal 对象中取出 Looper,若 Looper 存在就返回 Looper,否则返回空。其实,就是在 Looper.prepare() 方法中给 sThreadLocal 设置 Looper 如下:

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 对象!
然而,主线程中的 Handler 为何没有调用 Looper.prepare() 方法,且不会崩溃呢?事实上,程序在启动时,系统已经自动调用了 Looper.prepare() 方法。ActivityThread 中的 main() 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {  
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
EventLogger.setReporter(new EventLoggingReporter());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}

第 7 行,调用了 Looper.prepareMainLooper() 方法,该方法在调用 Looper.prepare() 方法,如下所示:

1
2
3
4
5
6
7
public static final void prepareMainLooper() {  
prepare();
setMainLooper(myLooper());
if (Process.supportsProcesses()) {
myLooper().mQueue.mQuitAllowed = false;
}
}

亦即应用程序的主线程中会始终存在一个 Looper 对象,则毋需手动调用 Looper.prepare() 方法。

综上,一句话总结 Handler 的创建过程即主线程中可以直接创建 Handler 对象,子线程中需要先调用 Looper.prepare() 方法才能接着创建 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 中提供了很多个发送消息的方法,除了 sendMessageAtFrontOfQueue() 方法外,别的发送消息的方法终归会调用到 sendMessageAtTime() 方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

该方法接收两个参数,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 行,入队即将所有的消息按时间 uptimeMillis 进行排序,亦即根据时间的顺序调用 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() 方法,该方法即消息队列的出队方法,过程即是若当前 MessageQueue 中存在 mMessages (待处理消息) ,会将该消息出队,再让下一条消息成为 mMessages,否则会进入阻塞状态,一直等到有新的消息入队。源码的第 14 行,每当有一个消息出队,就将它传递到 msg.target 的 dispatchMessage() 方法中,从之前的 sendMessageAtTime() 方法中的第 6 行看出,msg.target 即 Handler。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() 方法,将消息对象作为参数传递过去。

参看 Android 的官方文档,最标准的异步消息处理线程的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
class LooperThread extends Thread {  
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}

示意图如下图所示:

除了发送消息,还有以下几种方法可以在子线程中进行 UI 操作:Handler 的 post(),View 的 post() 和 Activity 的 runOnUiThread() 方法。

Handler 的 post() 方法代码如下:

1
2
3
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 对象。实际上,在文章前面 Handler 的 dispatchMessage() 方法中,若 Message 的 callback 等于 null,会调用 handleMessage() 方法,否则会调用 handleCallback() 方法。该方法的源码如下:

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

直接调用一开始传入的 Runnable 对象的 run() 方法。

综上,在子线程中通过 Handler 的 post() 方法进行 UI 操作可写成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends Activity {  
private Handler handler;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
// 在这里进行UI操作
}
});
}
}).start();
}
}

在 Runnable 对象的 run() 方法里更新 UI,效果与在 handleMessage() 方法中更新 UI 等同。

再看 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 中的 runOnUiTread() 方法代码如下:

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() 方法。

通篇文章下来,显然,无论使用哪种方法在子线程中更新 UI,背后的原理都是一样的,都需要借助异步消息处理机制进行实现,本篇至此结束。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.https://developer.android.com/reference/android/os/Handler.html
2.http://blog.csdn.net/guolin_blog/article/details/9991569