是这样的
今天凌晨三点🥱,被电话铃声吵醒,我勉强地拿起手机打开免提:
“喂,哪位啊?”
“我们分手吧,如果你再工作生活乱成一团糟。”,对面狠狠地说。
“哦,是二胖啊!”,我擦了擦冷汗!
“这是她发给我的微信最后一条消息,之后我的微信和手机都被拉黑了,我现在是实在没办法了,只好求助兄弟你!”
于是我给二胖分析:她需要你有一个长远的目标,并能够带着她一起进步。
这么说来好像Android消息机制:
- 如果你们组成了家庭,你们家庭就相当于一个进程
- 你的生活和她的生活就相当于进程中的两个线程
- 你和她分别是自己所属生活的Handler
- 制定一个长远的目标,目标使你有动力,动力就相当于Looper
- 根据长远目标制定短期目标,短期目标就相当于Message
- 把短期目标规划到日程表中,日程表就相当于MessageQueue
“二胖,我发你张图你看看”
二胖看了一会儿声音低沉地表示,“我没有目标,所以没有Looper,我的目标从哪找呢?”
于是我接着向二胖介绍:
二胖的Looper在哪里之ThreadLocal
二胖的目标一定与他的生活相关。
我们先来看下ThreadLocal类图概览
我们发现Thread有一个ThreadLocalMap类型的成员变量。ThreadLocalMap有一个保存Entry的数组。说明:
Thread和ThreadLocalMap是一对一的关系
ThreadLocalMap和Entry是一对多的关系
ThreadLocal依赖Thread
接下来再来分析下源码,ThreadLocal
是怎么get
和set
的
// 是一个泛型类
public class ThreadLocal<T> {
/**
* @param t Thread
* @return t持有的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
// Step 1 获取当前线程
Thread t = Thread.currentThread();
// Step 2 获取当前线程的数据集合
ThreadLocalMap map = getMap(t);
// Step 3 保存数据操作
// 如果Map不为null,则保存数据
if (map != null) {
map.set(this, value);
} else {
// 否则创建Map并保存
createMap(t, value);
}
}
public T get() {
// Step 1 获取当前线程
Thread t = Thread.currentThread();
// Step 2 获取当前线程下的Map
ThreadLocalMap map = getMap(t);
// Step 3 获取数据操作
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 初始化Map
return setInitialValue();
}
}
我们发现在
get
和set
方法中,通过调用Thread.currentThread()
来获取当前线程,然后再获取当前线程的ThreadLocalMap
。所以我们得出结论:
- ThreadLocal是以线程为界限,保存::在线程作用域范围内::所需要的数据。
- 一个ThreadLocal仅能保存一个数据
- ThreadLocal和Thread是多对一的关系,也就是说,一个线程可以创建多个ThreadLocal
通过ThreadLocal
,二胖了解了自己的目标与动力藏在生活中。
二胖找到自己的动力并模拟规划日程之Looper与MessageQueue
我们先来看一个简单小例子,当在子线程定义Handler的时候,一般的写法是这样的:
public class MyThread extends Thread {
Handler mHandler;
@Override
public void run() {
// Step 1 Looper做准备工作
Looper.prepare();
// Step 2 创建Handler
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
// Step 3 开始循环
Looper.loop();
}
}
总共有三个步骤:那么问题来了,Looper和Handler之间是什么关系呢?我们先来看下类图:
通过类图我们发现:
Looper与Thread、MessageQueue的关系
- Looper类定义了一个静态的ThreadLocal,用来存储Looper对象
- 一个Looper持有一个Thread的引用
- 一个Looper持有一个MessageQueue的引用
Handler与Looper、MessageQueue的关系
- 一个Handler持有一个Looper的引用
- 一个Handler持有一个MessageQueue的引用
但这只是通过类图看出来的,还没有和代码整合到一起,我们来通过源码分析
二胖找到自己的Looper
并买了一本日程表之Looper.prepare()
public final class Looper {
// 用于保存Looper的ThreadLocal,静态。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
// Step 1 如果ThreadLocal存在Looper,则抛出异常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// Step 2 创建一个looper保存到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
// Step 1 创建一个消息队列
mQueue = new MessageQueue(quitAllowed);
// Step 2 获得当前线程
mThread = Thread.currentThread();
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
Looper
定义了一个静态ThreadLocal
,Looper.prepare()
会先判断sThreadLocal
里是否已经存在Looper
,如果存在则抛出异常。如果不存在则创建一个Looper
保存到ThreadLocal
里。这可以表明:Thread
和Looper
是一对一的关系,在创建Looper
的时候,Looper
知道自己是属于MyThread
线程的Looper
,会同时创建一个MessageQueue
,所以Thread
、Looper
、MessageQueue
他们之间都是一对一的关系。
二胖找到自己的Looper
并买了一本日程表之new Handler()
public class Handler {
...
final Looper mLooper;
final MessageQueue mQueue;
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
...
// 1.初始化Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 2.初始化MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
...
}
由于已经执行了Looper.prepare()
,此时Looper
已经创建并保存在sThreadLocal
里,当new Handler()
时,Handler
知道了已经创建的Looper
在哪里,通过Looper
找到已经创建了的MessageQueue
。
-
Handler
和Looper
是多对一的关系 -
Handler
和MessageQueue
是多对一的关系,即一个线程可以创建多个Handler
-
Handler
的创建必须在调用Looper.prepare()
之后,否则会抛出异常
二胖鼓足了动力之Looper.loop()
public final class Looper {
public static void loop() {
// Step 1.获得当前线程的Looper
final Looper me = myLooper();
// Looper为空抛出异常,必须先调用Looper.prepare()来初始化Looper
if (me == null) {
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}
// Step 2.获得Looper持有的消息队列
final MessageQueue queue = me.mQueue;
...
// Step 3.开启无限循环
for (;;) {
...
// Step 4.从队列中取出一条消息,可能阻塞
Message msg = queue.next(); // might block
// msg为null,消息队列已经停止了
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
// Step 5.派送给msg的目标Handler去处理消息
msg.target.dispatchMessage(msg);
} finally {
...
}
// Step 6.回收消息
msg.recycleUnchecked();
}
}
}
当调用Looper.loop
的时候,首先获得属于当前线程的Looper
和MessageQueue
,并开启无限循环,不断地MessageQueue
中取出消息,如果能够取出消息则发送给这条消息的所属Handler
去处理,最后回收消息。如果取不出消息,则当前线程会阻塞在Step 4
,直到能够取出一条消息,再进行之后的步骤。
那么当调用
queue.next()
的时候是怎么从MessageQueue中取出一条消息的呢?
MessageQueue出队操作
public final class MessageQueue {
// 消息队列的队首
Message mMessages;
Message next() {
...
// 下一条消息出队时间
int nextPollTimeoutMillis = 0;
// 无限循环
for (;;) {
...
// Step 0 等待nextPollTimeoutMillis长的时间,唤醒线程
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// Step 1 当前队头msg
Message msg = mMessages;
// 存在msg,但是这个msg不知道处理它的事务的目标Handler是谁
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
// 取下一个Message
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// Step 2 如果msg还没有到要处理的时间(延时的消息),则获取距离处理这条msg的时间差,
// 待下次循环调用nativePollOnce时,当前线程处于等待状态,直到要处理msg的时间时唤醒线程
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 {
// Step 3 获取消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
// 队首msg出队列,直接后继走到队首的位置
mMessages = msg.next;
}
// 解除出队的msg和其直接后继的关联
msg.next = null;
// 标记这个msg已经被使用了
msg.markInUse();
// 返回这个出队的msg
return msg;
}
}
...
}
}
}
}
MessageQueue
持有对队首Message
的引用,那么当Looper.loop()
中调用next()
时,(Step 1)首先获取当前队列的第一条消息,然后判断是否到达处理这条消息的时间,(Step 2)如果没有当前还没有到达要处理这条消息的时机,则计算出当前时间距离这条消息处理时间的时间差nextPollTimeoutMillis
,待下次循环时,(Step 0)线程将等待nextPollTimeoutMillis
长时间再做出队操作。如果当前已经到达要处理这条消息的时间,则出队msg
并返回。所以Looper
在loop()
中就拿到了当前出队的消息。
从以上逻辑我们发现,
next()
操作时从队首消息,next,next依次访问的,如果当前消息还未到处理时间,则会等待,并不会访问下一条消息。那么假设队列中有A、B、C三条消息,当访问A时,发现此刻还未到处理A消息的时间,但已经到处理B消息的时间,线程却处于等待状态。如果是这样的话太不科学,由此我们猜测,消息队列是按照消息处理时间紧急程度来排列的,并不是按照先进先出排列的。那么我们来验证一下吧!
二胖开始短期目标日程规划之MessageQueue#enqueueMessage
当我们在其它线程调用mHandler.sendMessage(msg)
向MyThread
发送一条消息时,在多层调用后调用Handler
的enqueueMessage
public class Handler {
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1.target在此处初始化
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 2.调用MessageQueue的入队列操作
return queue.enqueueMessage(msg, uptimeMillis);
}
}
然后再调用MessageQueue
的enqueueMessage
进行入队操作
public final class MessageQueue {
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
...
msg.markInUse();
msg.when = when;
// Step 1 指向队首的引用
Message p = mMessages;
boolean needWake;
// Step 2 没有队首msg 或者 when == 0 表示不延时,立即发送(所以入队首)
// 或者入队msg的延时小于队首的延时(比队首先发送,所以插入队首)
if (p == null || when == 0 || when < p.when) {
// 把msg放置在队首
msg.next = p;
// 更新对队首的引用为msg
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// Step 3 如果队列存在 并且 加入的msg延时大于队首的延时,则遍历队列,插入到适当的位置
for (;;) {
// 临时变量指向P
prev = p;
// 后继
p = p.next;
// 不存在后继 或者 新插入的msg延时小于 后继的延时
if (p == null || when < p.when) {
// 跳出循环
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 把msg插入到prev和p之间变成 prev -> msg -> p
msg.next = p;
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
}
通过分析源码我们知道有两种情况
判断是否插入队首
- 不存在队首,即队列不存在
- 存在队首但是当前入队msg是立刻要发送的
- 存在队首但是当前入队msg延时时间小于队首消息延时时间
以上三种情况是或者的关系,只要存在其中一种情况,就把当前入队消息插入队首
遍历插入到合适的位置
- 存在队首
- 要入队的消息延时大于队首的延时
以上两种情况是且的关系,循环遍历队列根据延时进行插入操作。
我们发现,只要通过enqueueMessage
来入队的消息,msg.target
一定不为空,否则会抛出异常。
结语
自从二胖学会了Android消息机制走上人生巅峰后,他们夫妻二人愈发恩爱了。
这是一个深夜,桌子边的喜帖在灯光下闪闪发光,敲着代码的我有些黯然神伤!