android消息机制总结
1. 使用场景
Android启动后,会创建一个进程,以后所有的activity、service都在这个进程里面运行。启动初,该进程只包含一个线程,叫做UI主线程,负责处理UI界面的显示、更新。对于一些费时的操作,如查询、下载数据等不可以放到UI线程中,因为这可能会引起UI线程阻塞,导致系统崩溃。所以,这些费时操作需要单独启动一个子线程去处理。子线程处理完毕将结果通知给UI主线程,主线程得到结果后更新UI界面。子线程如何与UI主线程进行通信?在android中使用了消息机制来完成线程之间的通信。
2. 场景分析
如何使用消息机制来解决上面的问题?假设UI线程有一个自己的消息队列,简称UI_MQ,UI_MQ用于存储UI线程需要处理的消息。子线程需要与UI线程通信时将消息发送到UI_MQ中即可。完成这些操作需要解决下面的问题:
(1) 为UI线程创建一个唯一的消息队列UI_MQ
(2) 子线程将消息发送到UI线程对应的消息队列UI_MQ
(3) UI线程循环查询UI_MQ是否有消息,如果有取出处理。
解决上面三个问题不难,对应的方法如下:
(1) 创建一个链表作为UI线程消息队列保存消息
(2) 子线程持有对UI_MQ的引用,便可以将消息加入到UI_MQ
(3) UI线程设置一个循环等待方法,用于处理UI_MQ。每当发现有消息存在,取出处理。
上面描述了消息推送的基本实现,如果设计一个好的消息通信框架,需要对此进一步完善,下面看android中的消息机制是怎么实现的。
3. 整体架构
Anroid中消息机制包括下面几个概念:对应的类图如图3.1所示。
(1) Thread 线程
(2) Message 消息
(3) MessageQueue 消息队列,每个线程里有唯一一个消息队列。
(4) MessagePool 消息池。
(5) Looper 循环查询消息队列,每个线程里有唯一一个消息循环。
(6) Handler 处理消息。作用有创建消息、发送消息、处理消息。
(7) ThreadLocal 保证每个线程只创建唯一一个Looper和MessageQueue。
“场景分析”提到,子线程持有UI线程消息队列UI_MQ的引用便可以将消息发送到UI_MQ中。Android中不是这么实现,android通过Handler创建消息、发送消息、处理消息。Handler是如何完成这些功能的呢?
Handler创建消息
每一个消息都需要被指定的Handler处理,通过Handler创建消息便可以完成此功能。Android消息机制中引入了消息池。Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。消息池提高了消息对象的复用,减少系统垃圾回收的次数。
//Handler.java
//Handler构造时会获取Looper对象,这个对象是单例的,下面有介绍,并获取Looper对象对应的消息队列
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;
}
//可以通过Handler创建消息,并将消息发送到Handler所在线程对应的消息队列中。以后,Looper调用looper()函数后会循环遍历消息队列处理消息
public final boolean sendMessageAtFrontOfQueue(Message msg)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, 0);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
消息的创建流程如图3.2所示。
Handler发送消息
UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。
//Looper.java
public static final void prepare() {
//如果之前有过初始化,直接抛异常。ThreadLocal的作用就是保证每个线程最多创建一个Looper对象
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
//调用静态方法loop()以后,便开始了对消息队列的遍历
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
//if (!me.mRun) {
// break;
//}
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
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();
}
}
}
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
Handler、Looper、MessageQueue的初始化流程如图3.3所示。
Hander持有对UI主线程消息队列UI_MQ和消息循环Looper的引用,子线程可以通过Handler将消息发送到UI线程的消息队列UI_MQ中。
Handler处理消息
UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。
子线程通过Handler、Looper与UI主线程通信的流程如图3.4所示。
图 3.4 子线程通过Handler Looper与UI主线程进行通信
//Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); //具体处理由对应的Handler实现
}
}