作安卓的同窗對Handler並不陌生,經過它能夠方便的實現線程切換及定時任務,是線程通訊的利器。安卓應用自己就是事件驅動的消息輪詢模型,系統中的各個組件的正常工做也離不開Handler的支持。其背後的Message、Looper、MessageQueue爲這套機制提供了充足的保障。相信你們已經看過很多消息循環機制的源碼分析了,出於學習目的,此次讓咱們一塊兒實現一個簡單的消息循環機制。
java
咱們但願在javaSE上實現一個跨平臺的消息循環機制,能夠作到線程間通信並支持發送延時消息。線程同步方面選用jdk提供的重入鎖(非必須,可選其餘)配合Condition完成線程的休眠與喚醒。與AndroidSDK相似,經過MessageQueue對象實現消息隊列,經過Handler對象實現消息的發送與處理。但Message與Looper都相應作了簡化,只實現核心部分。放一張類圖鎮鎮場子:安全
消息隊列組織爲鏈表形式,經過MessageQueue對象維護。Handler建立一條消息,將其插入到MessageQueue維護的鏈表中。由於有延時消息的存在,隊列中的消息並不必定要當即處理。MessageQueue的next方法會檢查消息隊列第一條消息的處理時間,若符合要求才將Message出隊,Looper會將出隊的Message派發給這個Message對象內的Handler(也就是建立這個Message的Handler)來處理。因爲MessageQueue的next方法一次只會處理一條消息,因此Looper的loop方法會無限調用next,循環不斷處理消息,直至手動退出。bash
因此Message對象中有指向下一個節點的引用。when字段記錄了消息但願被處理的時間。Message中的其餘字段定義以下:函數
public class Message {
public Object obj; //你懂得
public int what; //你懂得
Handler target; //處理此Message的Handler(也是生成此Message的Handler)
Runnable callback;//消息處理回調
long when; //處理時間
Message next; //next節點
}複製代碼
定義好Message對象後,就能夠着手實現消息隊列了。學過操做系統的同窗都知道,這套消息循環機制其實就是一個生產者消費者模型,也能夠認爲是它的一個變種。消費者(Looper)不斷消費Message,直到緩衝區中沒有消息被阻塞。生產者(Handler)不斷向緩衝區生產Message,沒有容量限制。綜上所述,雖然咱們並不須要處理生產者線程的喚醒問題,但咱們仍需考慮如下幾點:oop
/** * 從消息隊列中取出一個Message 可能會引發線程的阻塞 * @return 須要處理的消息 */
public Message next() {
//須要休眠的時間 0表明緩衝區空時無限休眠 大於零表明實際的休眠時間 小於零表明不休眠
long waitTimeMillis = 0;
while (true) {
try {
lock.lockInterruptibly();
//若是沒有須要立刻處理的消息,此方法將在這行代碼處阻塞
waitMessage(waitTimeMillis);
//quiting爲布爾值變量做爲消息循環退出的標誌
if (quitting) return null;
long now = System.currentTimeMillis();
Message msg = messages;
//若是緩衝區內有數據,則以隊首元素中的時間字段爲依據
//要麼取出消息並返回,要麼計算等待時間從新休眠
if (msg != null) {
if (now < msg.when) {
waitTimeMillis = msg.when - now;
} else {
//隊首元素出隊
messages = messages.next;
msg.next = null;
return msg;
}
} else {
//緩衝區中沒數據,但線程被喚醒,說明消息循環須要退出,將等待時間置爲-1以便退出循環
waitTimeMillis = -1;
}
} catch (InterruptedException e) {
return null;
} finally {
lock.unlock();
}
}
}複製代碼
多說一句,上文提到的緩衝區、消息隊列、MessageQueue均指代這個由Message對象構成的鏈表,而消費者、消息循環指代MessageQueue所在的線程。上文的代碼中,lock對象爲重入鎖,定義以下:源碼分析
private ReentrantLock lock = new ReentrantLock();複製代碼
messages爲鏈表表頭引用,初始狀況下爲nullpost
private Message messages;
複製代碼
咱們知道,每次調用next方法都會返回一個Message對象,若是消息隊列中沒有合適的對象,此方法將阻塞,當消息循環退出時,next方法將直接返回null,MessageQueue的使用者(Looper)循環不斷的經過next方法取出一條條消息,根據Message爲空與否,決定消息循環的終止與運行。雖然next方法一次只返回一條消息,但其主體是一個循環。由於咱們須要處理延時消息,當一條消息入隊時,可能正在阻塞着的next方法將被迫調度起來繼續執行,但由於此時消息的處理時間還沒到,while循環能夠幫助咱們在下一輪循環中繼續休眠。也就是說,waitMessage方法返回後,雖然會保證緩衝區非空,但不能保證隊首的Message可被當即處理,因此咱們能夠看到這段代碼:學習
if (now < msg.when) {
waitTimeMillis = msg.when - now;
}複製代碼
在隊首Message不能當即被處理的狀況下,從新記錄休眠時間,通過while的下一輪循環,被記錄的休眠時間將被waitMessage方法處理:ui
public void waitMessage(long waitTimeMillis) throws InterruptedException {
if (waitTimeMillis < 0) return;
if (waitTimeMillis == 0) {
//緩衝區空則無限休眠,直到新的消息到來喚醒此線程
while (messages == null) notEmpty.await();
} else {
//休眠指定時間
notEmpty.await(waitTimeMillis, TimeUnit.MILLISECONDS);
}
}複製代碼
else分支中線程在notEmpty上等待waitTimeMillis毫秒。代碼中的notEmpty爲Condition對象,用於阻塞消費者線程,定義以下:this
private Condition notEmpty = lock.newCondition();複製代碼
回到waitMessage方法,當waitTimeMillis爲-1時,函數直接返回,無需等待。這是由於-1表明着線程準備退出,此時直接返回意味着再也不阻塞當前線程,代碼繼續向下執行,遇到if (quitting) return null;以後next方法便返回了。
這樣,quit方法的負責修改quitting字段的值,同時喚醒線程以便完成退出。
public void quit() {
lock.lock();
quitting = true;
notEmpty.signal();
lock.unlock();
}複製代碼
剩下的事情就很明確了。enqueueMessage方法用於入隊消息,定義以下:
public boolean enqueueMessage(Message message) {
try {
lock.lockInterruptibly();
if (quitting) return false;
//將message插入合適的位置
insertMessage(message);
//喚醒消費者線程
notEmpty.signal();
return true;
} catch (InterruptedException e) {
} finally {
lock.unlock();
}
return false;
}複製代碼
代碼很簡單,先將參數Message插入消息隊列,而後喚醒消費者線程。咱們直接來看一下insertMessage方法:
private void insertMessage(Message msg) {
Message now = messages;
if (messages == null || msg.when < now.when) {
msg.next = messages;
messages = msg;
return;
}
Message pre = now;
now = now.next;
while (now != null && now.when < msg.when) {
pre = now;
now = now.next;
}
msg.next = now;
pre.next = msg;
}複製代碼
這裏首先處理的是緩衝區空或新消息被插入到隊首的狀況,頭插法返回之。其餘狀況下,找到第一個比msg晚的Message對象,將其插入到這個對象以前。這樣就能夠保證入隊的消息在緩衝區中按處理時間升序排列了。
以上就是MessageQueue的所有代碼。如今咱們假設有三條消息在同一時間點被依次發送,第一個消息須要延時5s,第二個須要馬上處理,第三個須要延時2.5秒。初始狀態下緩衝區爲空,消費者線程阻塞。第一個消息的入隊使得消費者線程被喚醒,在next方法中檢查隊首元素髮現須要延時5s處理,因而將waitTimeMillis置爲5000,在下一輪while循環中調用notEmpty.await(waitTimeMillis, TimeUnit.MILLISECONDS);進行休眠。第二個消息的到來又致使了消費者線程被喚醒,此時第二個消息由於須要當即執行被插入緩衝區隊首。next方法取得隊首消息發現須要當即處理,便將此消息返回給Looper處理。Looper處理完後繼續調用next方法獲取消息,因爲此時緩衝區非空,無需阻塞,查看隊首消息(延時5s的那條消息)發現時間未到,計算剩餘時間並休眠本身。隨着第三條消息的到來,消費者線程又被喚醒,依然是檢查時間並休眠本身,注意,此時消息隊列中存在兩條消息,依次爲延時2.5s消息、延時5s消息,此次的休眠時間也被重置爲約2.5s。等時間一到,取出隊首元素返回給Looper,後面的動做與處理完無延時消息後的動道別無二致了。固然上面的描述只是可能會出現的一種狀況,具體的入隊與喚醒順序取決於操做系統對線程的調度,相信你們本身也能捋出來了。
有了MessageQueue對象,其餘角色的工做就輕鬆許多。上文中屢次提到Looper,他長這個樣子:
public class Looper {
private static ThreadLocal<Looper> localLooper = ThreadLocal.withInitial(Looper::new);
private MessageQueue queue;
private Looper() {
queue = new MessageQueue();
}
public static Looper myLooper() {
return localLooper.get();
}
public static void loop() {
Looper me = myLooper();
while (true) {
Message msg = me.queue.next();
if (msg == null) return;
msg.target.dispatchMessage(msg);
}
}
MessageQueue getMessageQueue() {
return queue;
}
public void quit() {
queue.quit();
}
}複製代碼
爲了簡化編寫,去掉了安卓Looper中的prepare方法,直接在聲明時初始化threadLocal。loop方法也很是的簡單粗暴,不斷調用MessageQueue的next方法,獲取消息並分發之,在分發的時候,調用的是msg.target.dispatchMessage(msg)方法。這裏的target對象是一個Handler對象:
public class Handler {
private Looper looper;
private Callback callback;
public Handler(Looper looper) {
this(looper, null);
}
public Handler() {
this(Looper.myLooper(), null);
}
public Handler(Looper looper, Callback callback) {
this.looper = looper;
this.callback = callback;
}
protected void handleMessage(Message message) {
}
void dispatchMessage(Message message) {
if (message.callback != null) {
message.callback.run();
return;
} else {
if (callback != null) {
if (callback.handleMessage(message)) {
return;
}
}
handleMessage(message);
}
}
public void sendMessage(Message message) {
if (looper == null) return;
MessageQueue queue = looper.getMessageQueue();
message.target = this;
queue.enqueueMessage(message);
}
public void postDelay(Runnable runnable, long delay) {
Message message = new Message();
message.when = System.currentTimeMillis() + delay;
message.callback = runnable;
sendMessage(message);
}
public void sendMessageDelay(Message message, long delay) {
message.when = System.currentTimeMillis() + delay;
sendMessage(message);
}
public void post(Runnable runnable) {
postDelay(runnable, 0);
}
public interface Callback {
boolean handleMessage(Message message);
}
}
複製代碼
dispatchMessage方法的實現與官方的思路一致,分發順序也儘可能復刻原版代碼。Handler會經過構造函數獲取Looper,或者經過參數傳入,或是直接經過Looper的靜態方法獲取。有了looper對象,在發送消息的時候咱們就能夠向looper內的MessageQueue插入消息了。摘出兩段有表明性的發送消息的代碼:
public void sendMessageDelay(Message message, long delay) {
message.when = System.currentTimeMillis() + delay;
sendMessage(message);
}
複製代碼
public void sendMessage(Message message) {
if (looper == null) return;
MessageQueue queue = looper.getMessageQueue();
message.target = this;
queue.enqueueMessage(message);
}複製代碼
在Handler發送消息以前,將本身綁定到message對象的target字段上,這樣looper就能夠將取出的message對象從新派發回建立它的handler,完成消息的處理。
好了,到此爲止就是這套消息循環機制的所有代碼,須要的小夥伴就麻煩本身整理下源碼吧,就這樣。
以上。