Android的消息机制主要是指Handler的运行机制和它所附带的MessageQueue和Looper的工作过程。
Handler、MessageQueue和Looper这三者其实是一个整体。但是我们要想清楚这个整体的工作流程就需要逐个击破。
下图罗列了这次分享说的重点
一、为什么提供这种机制
系统之所以提供这种机制主要是为了解决在子线程不能访问UI的矛盾。
那么问题来了......
1.为什么子线程不能访问ui呢?
因为Android的ui控件不是线程安全的。如果在多线程的情况下并发访问就会导致ui控件处于不可预期的状态。
2.那么在这种情况下为什么不对ui控件的访问加上锁机制呢?
首先加上锁的机制会使访问ui控件的逻辑变得复杂,其次因为锁机制会阻塞某些线程的进行,从而降低UI访问的效率。基于这两个原因最好的办法就是使用单线程处理UI控件。反之对于开发者来说只需要利用Handler切换一下线程就可以了,也不是很麻烦
二、MessageQueue(消息队列)
Android的消息队列是MessageQueue,它主要有两个功能:插入和读取。
它的内部实现是并不如它名字那样是个队列,而是用一个单链表来维护消息列表,我们知道单链表的数据结构实现插入和删除的效率高。
插入的操作很简单,本质就是单链表的插入操作。
//MessageQueue的插入操作 -enqueueMessage方法
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
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;
}
}
......
}
读取的操作next方法,通过无限的循环来检查有没有新消息,当有新消息来的时候,next方法会返回这条消息并将其从单链表中移除。
//MessageQueue的读取操作(伴随着删除操作)-next方法
Message next() {
......
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());
}
.....
}
三、ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据和读取数据。
它的特点通俗点说就是,不同的线程访问同一个对象,它们通过ThreadLocal获得的值是不一样的。
还可以这么神奇?
首先它是一个泛型类
public class ThreadLocal<T> {}
其次通过set方法我们可以看到它使用了Map的数据结构,它的key就是线程,value就是该对象的值。
//获取到当前线程的该变量的值,如果没有则初始化一个。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法将线程对应的值进行存储
之后,通过get方法就可以获取到该对象在这个线程中的值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
最后抛出一个问题
为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且互不干扰?
因为不同的线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前的ThreadLocal的索引去查找对应的value值,这样不同的线程中取出的数组自然是不同的。简单的说就是它们所对ThreadLocal的读写操作仅限于各自的线程内部。
四、Looper的工作原理
我们知道,Handler的工作需要Looper,没有Looper的线程就会报错。
那么Looper是做什么的呢?
具体来说,就是不停的从MessageQueue中查看是否有新的消息,如果有消息就会立刻处理,否则就一直阻塞在那里等待新的消息。
如何为线程创建Looper?
很简单,通过Looper.prepare就可以为线程创建一个Looper,这样这个线程就有了属于他的Looper。之后调用Looper.loop()就可以开启消息循环。
Looper最重要的就是loop方法,它的工作原理也很简单,首先它是一个死循环,会调用MessageQueue的next方法不断的获取新的消息并处理,没有消息的时候就会阻塞在那里。直到Looper调用quit或者quitSafely方法。也就是说我们最后是要在合适的时候退出Looper的。否则就会一直循环下去。
五、Handler的工作原理
Handler的工作主要包含消息的发送和接收。
1.发送消息主要通过post和send的一系列方法实现
mHandler.postDelayed(runnable,0);
发送消息的过程只是向MessageQueue中插入一条消息,它的next方法就会把这条消息返回给Looper,Looper收到消息后就开始处理了。最终消息由Looper交由Handler处理。由于Looper里面使用了ThreadLocal<Looper>,所以它就能够将消息切换到指定的线程中执行。
2.处理消息
Looper将消息交由Handler处理,就会调用Handler的disaptchMessage方法。这时候就进入了处理消息的阶段。
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先,检查Message的callback是否为null,不为null就通过handleCallback处理消息。这个callback是一个Runnable对象,也就是我们在post方法里传递的Runnable参数。
其次检查mCallBack是否为null,不为空就调用handleMessage方法。CallBack是一种当我们不想通过派生子类创建Handler的另外一种实现方式。
最后调用Handler的handlerMessage来处理消息
到这里我们就聊完了Android的消息机制,它的作用就是很轻松的将某个任务或者说是消息切换到指定的线程(Handler所在的线程)中执行。本质上来说,它并不是专门用于更新UI,只是常常被用在更新UI上。