前言
Android的日常开发离不开消息机制,本主题主要就Android的消息机制进行学习与探讨。
Android消息机制主要是通过Handler
实现的,而Handler
底层需要MessageQueue
和Looper
来分别实现消息队列以及消息处理。
1. 概述
首先来看一下Handler
的作用。
根据上文所述,MessageQueue
是消息队列用于存储消息,Looper
是循环处理用于处理消息,那么Handler
是干嘛的呢?Handler
的主要作用就是将某一任务(如发送消息,处理消息)放到特定的线程中去执行。
-
为什么非要在特定线程中去执行?
这是由于Android规定了对UI进行操作只可以在主线程进行,在
ViewRootImpl
中有着UI操作线程的验证。具体如下://ViewRootImpl.java void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
-
为什么UI操作要规定只能主线程去执行?
首先,观察UI控件的源码就知道,控件是线程不安全的,因此多线程并行操作UI会导致UI控件状态不可控。
-
为什么不写成线程安全的呢?
线程安全意味着加锁,加锁意味着UI访问逻辑繁琐并且降低了UI访问的效率,毕竟加锁操作会阻塞其他一些线程的运行。
因此似乎只能够从主线程对UI进行操作,但是Android又说耗时操作呢最好别在主线程使用,否则可能会造成ANR(Application No Response)。
那么当我们想进行UI修改等耗时过程时就必须在主线程操作,该怎么办呢?
这就是Handler机制的工作了,Handler机制可以将UI操作线程切换到主线程来执行。
大概过程如下:
Handler
通过post方法将Runnable放到Looper
中进行处理,或者通过send方法将Message发送到Looper
中进行处理。
以postDelayed
方法为例,
//Handler.java
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
......
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
......
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
......
return queue.enqueueMessage(msg, uptimeMillis);
}
其内部调用流程大致如上,post方法实质上也是调用send方法,然后调用MessageQueue
中的enqueueMessage
,将消息加入到消息队列中。
//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {//Handler内部机制是自己加锁的
if (mQuitting) {//判断线程是否已经销毁
.....
}
//占用消息,并备注占用的时间
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 {
......
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
......
}
return true;
}
这一小节大致知道其中流程即可,不具体分析代码。
在此之后,Looper发现新消息到来,对消息进行处理。
这里有个挺有意思的逻辑关系,从MessageQueue
的注释中可以看到,MessageQueue
可以用来存储消息,但消息并不是直接加入到消息队列,而是通过Handler对象交给Looper进行调度的。
//MessageQueue.java
/**
* Low-level class holding the list of messages to be dispatched by a
* {@link Looper}. Messages are not added directly to a MessageQueue,
* but rather through {@link Handler} objects associated with the Looper.
*
* <p>You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/
在Looper
源码中,注释中给出了最终处理消息的方式。在内部也声明了线程以及消息队列。
捋一捋三者之间的关系,可以说Looper是指挥官,Handler是执行官,MessageQueue是兵营,Message是小兵。指挥官(Looper)负责排兵布阵并对小兵(Message)下达各种命令操作(处理消息),而命令操作是由执行官(Handler)传达到兵营(MessageQueue)并对小兵进行执行(handleMessage)。
//Looper.java
/**
......
* <pre>
* 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();
* }
* }</pre>
*/
public final class Looper {
private static final String TAG = "Looper";
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
......
}
这里也需要注意一点,Looper是运行在创建Handler所在的线程中的,这样Handler中的UI操作就会被切换到创建Handler所在的线程中去执行。
2. ThreadLocal
在正式介绍Handler机制前,先引入ThreadLocal的概念,这对Handler机制有着重大影响。
细心的人可能已经在Looper中发现了ThreadLocal这一定义了。
//Looper.java
public final class Looper {
......
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
......
}
ThreadLocal是一个仅仅存储线程本身数据的数据存储类。可以类比于代码中变量的作用域,出了这个线程(作用域)就获取不到线程内部的数据(变量)。
2.1 使用场景
-
当前数据只想在本线程使用,并且不同线程有着不同的副本
比如对于Handler来说,Looper就是这样,每个线程需要有自己的Looper来处理本线程的消息,那么Looper的作用域很明显就是本线程,因此ThreadLocal是个不错的选择。
-
复杂逻辑业务下的对象传递
比如监听器的传递,有些任务过于复杂,需要用到函数调用栈相对较深,并同时希望监听器能够贯穿整个线程。一般情况下,有两种解决方式,一种是将监听器作为参数一直传下去,一种是将监听器定义为静态变量。这两种方式都有问题,第一种,将监听器一直作为参数传递下去,听起来就十分有问题,这样的程序设计是非常笨重且不可理喻的。第二种,扩展性相对较差,预先不知道有多少个线程在执行或许就没法确定监听器个数,只能写死灵活性太差。
而ThreadLocal不会存在这样的问题,每个线程存储着自我的监听器,想要获取监听器可以写入一个get方法获取监听器。
2.2 小小实践
声明一个布尔型ThreadLocal变量。
如下,在主线程,线程1,线程2中分别打印日志,显示get值。
得出如下结果:
2.3 ThreadLocal源码解析
2.3.1 set方法
ThreadLocal
中set
方法,ThreadLocalMap
是ThreadLocal
的一个内部类,其实质就是一个映射Map,以ThreadLocal
为键,以ThreadLocal
中泛型T为值,用一个键值对数组table
进行存储,这样的话,有些像哈希表的开发地址法解决哈希冲突了。
ThreadLocalMap
的构建在代码中已经标注了,这一块的逻辑比较简单,相当于写了一个开放地址法处理哈希冲突的哈希表,自定义了一个Entry
用来接收键值对。
实例化一个ThreadLocal
对象,然后使用它的set
方法时会先判断当前线程是否有自己的ThreadLocalMap
,如果有就给当前线程设置一个键值对<ThreadLocal(自己实例化的对象), value>。
这个过程实质上就是ThreadLocal把自己保存到了相应Thread的ThreadLocalMap中。
//ThreadLocal.java
static class ThreadLocalMap {
.......
//键值对,弱引用?这个不是很懂
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//初始容量,扩容的话必须是2的次幂
private static final int INITIAL_CAPACITY = 16;
//键值对数组 长度必须为2的次幂
private Entry[] table;
//键值对个数
private int size = 0;
//扩容阈值
private int threshold; // Default to 0
//装载因子是长度的2/3倍数
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
//构造函数,初始化一个ThreadLocalMap
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];//数组长度
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//计算哈希值,放到相应的位置
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
......
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
上代码中Thread
类中有着ThreadLocalMap
类型的变量用来保存线程的ThreadLocal数据。
//Thread.java
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
2.3.2 get方法
//ThreadLocal.java
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
3. MessageQueue作用
消息队列(MessageQueue)用来存储Message,主要操作就是将消息插入(enqueueMessage()
)到队列中,将Message从队列中读取next()
出来。有趣的是,MessageQueue虽然成为队列但是实质上是一个链表。
//MessageQueue.java
public final class MessageQueue {
......
Message mMessages;
......
}
//Message.java
public final class Message implements Parcelable {
......
// sometimes we store linked lists of these things
/*package*/ Message next;
......
}
消息加入队列的操作上文在概述消息分发机制中已经梳理,下面主要是消息读取流程。
Message next() {
......
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {//无限循环直到返回消息
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
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());//循环查找消息
}
if (msg != null) {
if (now < msg.when) {
//新消息没有准备好进入队列,设置时间唤醒,同样产生阻塞
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;//得到消息
//从链表中删除该消息
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();//调度被处理过的消息
return null;
}
.......
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;//如果没有来的消息就进行阻塞
continue;
}
......
}
.......
}
}
可以看到当没有接收到新消息时,next方法就一直在循环中进行,产生阻塞(mBlock
);如果得到新消息,那么就会返回这个消息并且从链表中删除这条消息。
4. Looper作用
Looper的作用主要作用是循环检测消息队列中的消息,即loop
是Looper模块其主要作用的函数,从代码中可以看出,只有当没有消息在消息队列的时候才会跳出循环。
//Looper.java
public final class Looper {
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
//构造函数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//消息队列
mThread = Thread.currentThread();//Looper所在线程
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//获取线程的Looper
}
//循环主函数
public static void loop() {
final Looper me = myLooper();//拿到本线程Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
......
//Looper循环检测消息队列
for (;;) {
Message msg = queue.next(); //next方法从消息队列提取消息
if (msg == null) {
//只有消息为空时候,才退出
return;
}
......
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);//消息调度处理
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
msg.recycleUnchecked();//有消息,则打上inUse的标识
}
}
......
}
如果Looper接收到消息,就会调用msg.target
来处理消息,而msg.target
是Handler对象,调用Handler的dispatchMessage
方法来处理这个消息。
//Message.java
public final class Message implements Parcelable {
......
/*package*/ Handler target;
......
}
而在Looper发挥作用之前,需要做一些准备,即提前调用prepare()
创建一个Looper,然后再调用loop()
开启循环。但是很多时候我们在主线程似乎直接定义Handler并定义要发送的消息Message就可以实现消息机制了,这是因为ActivityThread即主线程已经创建了一个Looper,但是与其他方式不同,Looper专门为这个特殊的线程(也就是主线程)提供了prepareMainLooper
方法进行主线程Looper的定义,同时留有sMainLooper
和getMainLooper
用来获取主线程Looper。
//Looper.java
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));
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
当然,Looper也可以退出,Looper
类中有quit
和quitSafely
方法,但其实还是调用了消息队列的退出方法,然后消息队列将自己清空,于是Looper在循环中收到的msg = queue.next() => msg = null
,也退出循环,结束。
//Looper.java
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
//MessageQueue.java
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
5. Handler作用
经过以上流程,我们可以把过程相对具体化(列出关键函数)如下:
Handler通过post/send方法调用enqueueMessage
把消息加到MessageQueue中,然后Looper循环检测获取到新消息再调用Handler的dispatchMessage
对消息进行处理。
Handler的post与send方法已经看过了,现在就只剩dispatchMessage方法了。过程如下:
-
消息的回调接口不为空则直接调用消息的
callback
(Runnable),即为Handler的post的参数Runnable
。 -
如若为空,则判断自身接口
mCallback
是否为空,不为空则调用接口的处理消息机制,如果处理失败,最后还有一个子类重写的handleMessage
可以调用。 -
这里注意,一般我们定义Handler对象时,都会重写
handleMessage
方法,这样其实就跳过了第二步骤,因为本身并没有传入或者设置一个CallBack
接口。因此,其实也可以采用Handler的另一种构造函数设置CallBack
,在CallBack
接口的handleMessage
中处理消息。
//Message.java
/*package*/ Runnable callback;
//Handler.java
//m.callback即为post方法中的Runnable对象
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public interface Callback {
public boolean handleMessage(Message msg);
}
public void handleMessage(Message msg) {
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//不为空
handleCallback(msg);//则调用handleCallback
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
6. Android消息机制的过程图
总结
本文主要介绍了Android的消息机制并从源码层面解析Handler、MessageQueue、Looper三者如何协调工作。