是這樣的
今天凌晨三點(diǎn)??袁翁,被電話(huà)鈴聲吵醒,我勉強(qiáng)地拿起手機(jī)打開(kāi)免提:
“喂婿脸,哪位傲皇ぁ?”
“我們分手吧狐树,如果你再工作生活亂成一團(tuán)糟焙压。”抑钟,對(duì)面狠狠地說(shuō)涯曲。
“哦,是二胖霸谒幻件!”,我擦了擦冷汗心俗!
“這是她發(fā)給我的微信最后一條消息傲武,之后我的微信和手機(jī)都被拉黑了蓉驹,我現(xiàn)在是實(shí)在沒(méi)辦法了城榛,只好求助兄弟你!”
于是我給二胖分析:她需要你有一個(gè)長(zhǎng)遠(yuǎn)的目標(biāo)态兴,并能夠帶著她一起進(jìn)步狠持。
這么說(shuō)來(lái)好像Android消息機(jī)制:
- 如果你們組成了家庭,你們家庭就相當(dāng)于一個(gè)進(jìn)程
- 你的生活和她的生活就相當(dāng)于進(jìn)程中的兩個(gè)線程
- 你和她分別是自己所屬生活的Handler
- 制定一個(gè)長(zhǎng)遠(yuǎn)的目標(biāo)瞻润,目標(biāo)使你有動(dòng)力喘垂,動(dòng)力就相當(dāng)于Looper
- 根據(jù)長(zhǎng)遠(yuǎn)目標(biāo)制定短期目標(biāo),短期目標(biāo)就相當(dāng)于Message
- 把短期目標(biāo)規(guī)劃到日程表中绍撞,日程表就相當(dāng)于MessageQueue
“二胖正勒,我發(fā)你張圖你看看”
二胖看了一會(huì)兒聲音低沉地表示,“我沒(méi)有目標(biāo)傻铣,所以沒(méi)有Looper章贞,我的目標(biāo)從哪找呢?”
于是我接著向二胖介紹:
二胖的Looper在哪里之ThreadLocal
二胖的目標(biāo)一定與他的生活相關(guān)非洲。
我們先來(lái)看下ThreadLocal類(lèi)圖概覽
我們發(fā)現(xiàn)Thread有一個(gè)ThreadLocalMap類(lèi)型的成員變量鸭限。ThreadLocalMap有一個(gè)保存Entry的數(shù)組。說(shuō)明:
Thread和ThreadLocalMap是一對(duì)一的關(guān)系
ThreadLocalMap和Entry是一對(duì)多的關(guān)系
ThreadLocal依賴(lài)Thread
接下來(lái)再來(lái)分析下源碼两踏,ThreadLocal
是怎么get
和set
的
// 是一個(gè)泛型類(lèi)
public class ThreadLocal<T> {
/**
* @param t Thread
* @return t持有的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
// Step 1 獲取當(dāng)前線程
Thread t = Thread.currentThread();
// Step 2 獲取當(dāng)前線程的數(shù)據(jù)集合
ThreadLocalMap map = getMap(t);
// Step 3 保存數(shù)據(jù)操作
// 如果Map不為null败京,則保存數(shù)據(jù)
if (map != null) {
map.set(this, value);
} else {
// 否則創(chuàng)建Map并保存
createMap(t, value);
}
}
public T get() {
// Step 1 獲取當(dāng)前線程
Thread t = Thread.currentThread();
// Step 2 獲取當(dāng)前線程下的Map
ThreadLocalMap map = getMap(t);
// Step 3 獲取數(shù)據(jù)操作
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 初始化Map
return setInitialValue();
}
}
我們發(fā)現(xiàn)在
get
和set
方法中,通過(guò)調(diào)用Thread.currentThread()
來(lái)獲取當(dāng)前線程梦染,然后再獲取當(dāng)前線程的ThreadLocalMap
赡麦。所以我們得出結(jié)論:
- ThreadLocal是以線程為界限,保存::在線程作用域范圍內(nèi)::所需要的數(shù)據(jù)。
- 一個(gè)ThreadLocal僅能保存一個(gè)數(shù)據(jù)
- ThreadLocal和Thread是多對(duì)一的關(guān)系泛粹,也就是說(shuō)车荔,一個(gè)線程可以創(chuàng)建多個(gè)ThreadLocal
通過(guò)ThreadLocal
,二胖了解了自己的目標(biāo)與動(dòng)力藏在生活中戚扳。
二胖找到自己的動(dòng)力并模擬規(guī)劃日程之Looper與MessageQueue
我們先來(lái)看一個(gè)簡(jiǎn)單小例子忧便,當(dāng)在子線程定義Handler的時(shí)候,一般的寫(xiě)法是這樣的:
public class MyThread extends Thread {
Handler mHandler;
@Override
public void run() {
// Step 1 Looper做準(zhǔn)備工作
Looper.prepare();
// Step 2 創(chuàng)建Handler
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 處理消息
}
};
// Step 3 開(kāi)始循環(huán)
Looper.loop();
}
}
總共有三個(gè)步驟:那么問(wèn)題來(lái)了帽借,Looper和Handler之間是什么關(guān)系呢珠增?我們先來(lái)看下類(lèi)圖:
通過(guò)類(lèi)圖我們發(fā)現(xiàn):
Looper與Thread、MessageQueue的關(guān)系
- Looper類(lèi)定義了一個(gè)靜態(tài)的ThreadLocal砍艾,用來(lái)存儲(chǔ)Looper對(duì)象
- 一個(gè)Looper持有一個(gè)Thread的引用
- 一個(gè)Looper持有一個(gè)MessageQueue的引用
Handler與Looper蒂教、MessageQueue的關(guān)系
- 一個(gè)Handler持有一個(gè)Looper的引用
- 一個(gè)Handler持有一個(gè)MessageQueue的引用
但這只是通過(guò)類(lèi)圖看出來(lái)的,還沒(méi)有和代碼整合到一起脆荷,我們來(lái)通過(guò)源碼分析
二胖找到自己的Looper
并買(mǎi)了一本日程表之Looper.prepare()
public final class Looper {
// 用于保存Looper的ThreadLocal凝垛,靜態(tài)。
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 創(chuàng)建一個(gè)looper保存到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
// Step 1 創(chuàng)建一個(gè)消息隊(duì)列
mQueue = new MessageQueue(quitAllowed);
// Step 2 獲得當(dāng)前線程
mThread = Thread.currentThread();
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
Looper
定義了一個(gè)靜態(tài)ThreadLocal
梦皮,Looper.prepare()
會(huì)先判斷sThreadLocal
里是否已經(jīng)存在Looper
,如果存在則拋出異常桃焕。如果不存在則創(chuàng)建一個(gè)Looper
保存到ThreadLocal
里剑肯。這可以表明:Thread
和Looper
是一對(duì)一的關(guān)系,在創(chuàng)建Looper
的時(shí)候观堂,Looper
知道自己是屬于MyThread
線程的Looper
让网,會(huì)同時(shí)創(chuàng)建一個(gè)MessageQueue
,所以Thread
师痕、Looper
溃睹、MessageQueue
他們之間都是一對(duì)一的關(guān)系。
二胖找到自己的Looper
并買(mǎi)了一本日程表之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;
}
...
}
由于已經(jīng)執(zhí)行了Looper.prepare()
胰坟,此時(shí)Looper
已經(jīng)創(chuàng)建并保存在sThreadLocal
里因篇,當(dāng)new Handler()
時(shí),Handler
知道了已經(jīng)創(chuàng)建的Looper
在哪里腕铸,通過(guò)Looper
找到已經(jīng)創(chuàng)建了的MessageQueue
惜犀。
-
Handler
和Looper
是多對(duì)一的關(guān)系 -
Handler
和MessageQueue
是多對(duì)一的關(guān)系,即一個(gè)線程可以創(chuàng)建多個(gè)Handler
-
Handler
的創(chuàng)建必須在調(diào)用Looper.prepare()
之后狠裹,否則會(huì)拋出異常
二胖鼓足了動(dòng)力之Looper.loop()
public final class Looper {
public static void loop() {
// Step 1.獲得當(dāng)前線程的Looper
final Looper me = myLooper();
// Looper為空拋出異常虽界,必須先調(diào)用Looper.prepare()來(lái)初始化Looper
if (me == null) {
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}
// Step 2.獲得Looper持有的消息隊(duì)列
final MessageQueue queue = me.mQueue;
...
// Step 3.開(kāi)啟無(wú)限循環(huán)
for (;;) {
...
// Step 4.從隊(duì)列中取出一條消息,可能阻塞
Message msg = queue.next(); // might block
// msg為null涛菠,消息隊(duì)列已經(jīng)停止了
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
// Step 5.派送給msg的目標(biāo)Handler去處理消息
msg.target.dispatchMessage(msg);
} finally {
...
}
// Step 6.回收消息
msg.recycleUnchecked();
}
}
}
當(dāng)調(diào)用Looper.loop
的時(shí)候莉御,首先獲得屬于當(dāng)前線程的Looper
和MessageQueue
撇吞,并開(kāi)啟無(wú)限循環(huán),不斷地MessageQueue
中取出消息礁叔,如果能夠取出消息則發(fā)送給這條消息的所屬Handler
去處理牍颈,最后回收消息。如果取不出消息琅关,則當(dāng)前線程會(huì)阻塞在Step 4
煮岁,直到能夠取出一條消息,再進(jìn)行之后的步驟涣易。
那么當(dāng)調(diào)用
queue.next()
的時(shí)候是怎么從MessageQueue中取出一條消息的呢画机?
MessageQueue出隊(duì)操作
public final class MessageQueue {
// 消息隊(duì)列的隊(duì)首
Message mMessages;
Message next() {
...
// 下一條消息出隊(duì)時(shí)間
int nextPollTimeoutMillis = 0;
// 無(wú)限循環(huán)
for (;;) {
...
// Step 0 等待nextPollTimeoutMillis長(zhǎng)的時(shí)間,喚醒線程
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// Step 1 當(dāng)前隊(duì)頭msg
Message msg = mMessages;
// 存在msg新症,但是這個(gè)msg不知道處理它的事務(wù)的目標(biāo)Handler是誰(shuí)
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
// 取下一個(gè)Message
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// Step 2 如果msg還沒(méi)有到要處理的時(shí)間(延時(shí)的消息)步氏,則獲取距離處理這條msg的時(shí)間差,
// 待下次循環(huán)調(diào)用nativePollOnce時(shí)徒爹,當(dāng)前線程處于等待狀態(tài)荚醒,直到要處理msg的時(shí)間時(shí)喚醒線程
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 {
// 隊(duì)首msg出隊(duì)列,直接后繼走到隊(duì)首的位置
mMessages = msg.next;
}
// 解除出隊(duì)的msg和其直接后繼的關(guān)聯(lián)
msg.next = null;
// 標(biāo)記這個(gè)msg已經(jīng)被使用了
msg.markInUse();
// 返回這個(gè)出隊(duì)的msg
return msg;
}
}
...
}
}
}
}
MessageQueue
持有對(duì)隊(duì)首Message
的引用隆嗅,那么當(dāng)Looper.loop()
中調(diào)用next()
時(shí)界阁,(Step 1)首先獲取當(dāng)前隊(duì)列的第一條消息,然后判斷是否到達(dá)處理這條消息的時(shí)間榛瓮,(Step 2)如果沒(méi)有當(dāng)前還沒(méi)有到達(dá)要處理這條消息的時(shí)機(jī)铺董,則計(jì)算出當(dāng)前時(shí)間距離這條消息處理時(shí)間的時(shí)間差nextPollTimeoutMillis
,待下次循環(huán)時(shí)禀晓,(Step 0)線程將等待nextPollTimeoutMillis
長(zhǎng)時(shí)間再做出隊(duì)操作。如果當(dāng)前已經(jīng)到達(dá)要處理這條消息的時(shí)間坝锰,則出隊(duì)msg
并返回粹懒。所以Looper
在loop()
中就拿到了當(dāng)前出隊(duì)的消息。
從以上邏輯我們發(fā)現(xiàn)顷级,
next()
操作時(shí)從隊(duì)首消息凫乖,next,next依次訪問(wèn)的弓颈,如果當(dāng)前消息還未到處理時(shí)間帽芽,則會(huì)等待,并不會(huì)訪問(wèn)下一條消息翔冀。那么假設(shè)隊(duì)列中有A导街、B、C三條消息纤子,當(dāng)訪問(wèn)A時(shí)搬瑰,發(fā)現(xiàn)此刻還未到處理A消息的時(shí)間款票,但已經(jīng)到處理B消息的時(shí)間,線程卻處于等待狀態(tài)泽论。如果是這樣的話(huà)太不科學(xué)艾少,由此我們猜測(cè),消息隊(duì)列是按照消息處理時(shí)間緊急程度來(lái)排列的翼悴,并不是按照先進(jìn)先出排列的缚够。那么我們來(lái)驗(yàn)證一下吧!
二胖開(kāi)始短期目標(biāo)日程規(guī)劃之MessageQueue#enqueueMessage
當(dāng)我們?cè)谄渌€程調(diào)用mHandler.sendMessage(msg)
向MyThread
發(fā)送一條消息時(shí)鹦赎,在多層調(diào)用后調(diào)用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.調(diào)用MessageQueue的入隊(duì)列操作
return queue.enqueueMessage(msg, uptimeMillis);
}
}
然后再調(diào)用MessageQueue
的enqueueMessage
進(jìn)行入隊(duì)操作
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 指向隊(duì)首的引用
Message p = mMessages;
boolean needWake;
// Step 2 沒(méi)有隊(duì)首msg 或者 when == 0 表示不延時(shí)潮瓶,立即發(fā)送(所以入隊(duì)首)
// 或者入隊(duì)msg的延時(shí)小于隊(duì)首的延時(shí)(比隊(duì)首先發(fā)送,所以插入隊(duì)首)
if (p == null || when == 0 || when < p.when) {
// 把msg放置在隊(duì)首
msg.next = p;
// 更新對(duì)隊(duì)首的引用為msg
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// Step 3 如果隊(duì)列存在 并且 加入的msg延時(shí)大于隊(duì)首的延時(shí)钙姊,則遍歷隊(duì)列毯辅,插入到適當(dāng)?shù)奈恢?
for (;;) {
// 臨時(shí)變量指向P
prev = p;
// 后繼
p = p.next;
// 不存在后繼 或者 新插入的msg延時(shí)小于 后繼的延時(shí)
if (p == null || when < p.when) {
// 跳出循環(huán)
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;
}
}
通過(guò)分析源碼我們知道有兩種情況
判斷是否插入隊(duì)首
- 不存在隊(duì)首,即隊(duì)列不存在
- 存在隊(duì)首但是當(dāng)前入隊(duì)msg是立刻要發(fā)送的
- 存在隊(duì)首但是當(dāng)前入隊(duì)msg延時(shí)時(shí)間小于隊(duì)首消息延時(shí)時(shí)間
以上三種情況是或者的關(guān)系煞额,只要存在其中一種情況思恐,就把當(dāng)前入隊(duì)消息插入隊(duì)首
遍歷插入到合適的位置
- 存在隊(duì)首
- 要入隊(duì)的消息延時(shí)大于隊(duì)首的延時(shí)
以上兩種情況是且的關(guān)系,循環(huán)遍歷隊(duì)列根據(jù)延時(shí)進(jìn)行插入操作膊毁。
我們發(fā)現(xiàn)胀莹,只要通過(guò)enqueueMessage
來(lái)入隊(duì)的消息,msg.target
一定不為空婚温,否則會(huì)拋出異常描焰。
結(jié)語(yǔ)
自從二胖學(xué)會(huì)了Android消息機(jī)制走上人生巔峰后,他們夫妻二人愈發(fā)恩愛(ài)了栅螟。
這是一個(gè)深夜荆秦,桌子邊的喜帖在燈光下閃閃發(fā)光,敲著代碼的我有些黯然神傷力图!