序
由于Andoird的UI元素采用的单一线程模型,只能在UI线程进行更新,于是我们用来更新UI,常用Handler
进行更新,用法如下:
mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
public boolean handleMessage(Message msg) {
//do ui update
return false;
}
});
Message message = new Message();
mHandler.sendMessage(message);
用法简单明了,接着我们直接来体会一下 消息是如何被传送到主线程的。
0x00 有些什么?
Handler: 我们通过它来发送消息。
Looper: 构造Handler时,需要传入一个 Looper
对象。例子中我们传入了主线程的Looper。
MessageQueue:当我们使用 Handler
将一条Message
sendMessageDelayed 出去,在Handler源码中追踪,调用路径如下:
Handler.sendMessage
sendEmptyMessage
sendEmptyMessageDelayed
sendMessageAtTime
enqueueMessage
msg.target = handler;// set target -> Handler
MessageQueue.enqueueMessage(msg, uptimeMillis)
这边出现了我们的新的对象 MessageQueue
。很明显的,sendMessage的操作,其实是将Message 放入了MessageQueue中。
我们先来大致梳理一下3者的关系:
img简言之,我们在子线程,生成了一个持有主线程 Looper的Handler。
- 我们通过Handler将Message 传递到了主线程,加到了对应的MessageQueue中。
- 主线程的Looper读取Message,并且处理。
其中,有几个关键点:
- 如何找到 目标线程?
- MessageQueue如何对新增Message进行处理的?
- Message如何被消费的?
我们一个一个看。
0x01 Handler如何找到目标线程,并且发送Message?
从上面的sendXXX调用堆栈可以看出,最终调用到MessageQueue.enqueueMessage(msg, uptimeMillis)
。那么,Handler中的MessageQueue 是怎么来的呢?
我们可以从构造器中找到答案:
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
来自Looper,看记得那个Looper么?-> Looper.getMainLooper().
我们来看看Looper:
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
...
sMainLooper = myLooper();
...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这里出现了一个很重要的对象:sThreadLocal->ThreadLocal<T> 我们来看看它。
ThreadLocal 原理
这是一个用来做线程内部存储的类,通过它可以再指定的线程中获取、存储数据,并且只有在该线程里才能获取,其他线程无法获取。而我们的不同线程的Looper 就是放在ThreadLocal中。对外提供的方法很简单:
- public T get()
- public void set(T value)
我们大致看看实现:
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = current.localValues;
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = current.localValues;
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
class Values {
...
void put(ThreadLocal<?> key, Object value) {
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
...
}
}
...
}
主要思路是将数据结构放在了Thread.localValues,用某种算法进行了下标计算,得出 put、get的index。
Ok,那么现在,我们的Looper、以及它的MessageQueue都非常eazy的获取到了。
0x02 MessageQueue如何对新增Message进行处理的?
首先,我们需要了解一下MessageQueue的结构。最重要的一个变量是:Message mMessages; 。
而在Message也有几个重要的变量:
- Message next; 标志下一个Message
- Handler target 目标Handler是什么,用于做消息派发(还记得上面代码部分,有设置过么? msg.target)。
从中我们可以看出,MessageQueue是个单链表结构。
此时,我们再回到那个函数:MessageQueue.enqueueMessage(msg, uptimeMillis)
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
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 {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
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;
}
...
}
return true;
}
可以很明显看出,这是一个根据 msg.when 为依据,做的链表插入。这样,我们的Message就能按照正确时间的顺序,进行排列。
之外:
MessageQueue还有其他的操作,都是对单链表的操作。如:
- private void removeAllFutureMessagesLocked()
- void removeMessages(Handler h, Runnable r, Object object)
0x03 Message如何被消费的?
我们有一块很重要的东西,那就是Looper,来看看前面那张图,Looper 会从Message中,不断的读取 Message。是如何做到的呢? 我们试着来看看源码。
现在我们没有了头绪,转过来,我们先来看看主线程是怎么启动的。
主线程的启动过程中对Looper的处理。
我们知道,主线程启动,是通过ActivityThead.main(String[] args)
方法进行启动,我们一起看看:
public static void main(String[] args) {
..
Looper.prepareMainLooper();
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
...
}
里面有几行对于looper的处理,有2个关键的操作:
- Looper.prepareMainLooper
- Looper.loop();
Looper.prepareMainLooper
prepareMainLooper会接着调用到prepare(boolean quitAllowed)。
prepare的过程比较简单。我们大致看一眼Looper中的部分:
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));
}
还记得 上面的 ThreadLocal
吗?prepare的过程,其实就是生成一个Looper,往ThreadLocal 中塞。
Looper.loop();
这个是最核心的部分,我们依然上源码:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
...
msg.target.dispatchMessage(msg);
...
}
}
第一行获取myLooper()
的过程,也就解释了为什么要先做“prepare”操作。
接着,会是一个死循环,核心步骤2步:
- 不断的从MessageQueue.next 中读取Message。
- 并且将消息进行派发-msg.target.dispatchMessage(msg);
我们接着看这2步:
MessageQueue.next
这个方法会返回一个Message,但是是一个阻塞方法,在适当的时候才会返回,这也就是为什么我们可以使用sendxxxDelay(...)
在一段时间后才执行的原因。
我们来看源码:
Message next() {
...
for (;;) {
...
synchronized (this) {
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) {
// Next message is not ready. Set a timeout to wake up when it is ready.
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;
}
...
}
...
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
为了好看,缩减了一些片段。next() 方法,里面也有一个死循环,当message.when到了之后,才会返回。
消息派发:msg.target.dispatchMessage(msg);
这步能够找到正确的target,完全依赖于在 Handler.sendMessage() 做 enqueueMessage 时设置的:msg.target = handler;
所以,能够用正确的Handler进行派发,Handler派发逻辑:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
注意哦,这里进行消息处理,是Looper调用过来的,因此是在 目标线程。
0x04 总结
我们再来回忆一下这张图:
img发送消息流程:
- 新建Handler,指定Looper。
- 调用sendXXXX方法。
- Handler将消息发送到 Looper.MessageQueue中。
注意几个细节:
- Looper通过ThreadLocal找到。
- 当一个线程需要用Looper时,要先调用Looper.prepare(),目的就是为了在ThreadLocal中创建当前线程的Looper。
消息处理过程:
- 每个线程调用Looper.loop(),是个死循环,会不断的从MessageQueue.next读消息进行处理。
- MessageQueue.next也是个阻塞函数,会等到有可处理的消息时返回Message对象。
- 对Message消息,会调用message.target.dispatchMessage进行消息传递。
注意几个细节:
- 主线程已经进行了Looper.prepareMainLooper,子线程也可以用handler进行消息传递,但是得先调用 Looper.prepare() 为当前线程创建Looper。
网友评论